<?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: Peter</title>
    <description>The latest articles on DEV Community by Peter (@smeldr).</description>
    <link>https://dev.to/smeldr</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%2F3988634%2F48c095ea-731e-40a3-aaae-d4c793825c3a.png</url>
      <title>DEV Community: Peter</title>
      <link>https://dev.to/smeldr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/smeldr"/>
    <language>en</language>
    <item>
      <title>No pipeline, no oil spills</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Mon, 06 Jul 2026 19:32:54 +0000</pubDate>
      <link>https://dev.to/smeldr/no-pipeline-no-oil-spills-23oh</link>
      <guid>https://dev.to/smeldr/no-pipeline-no-oil-spills-23oh</guid>
      <description>&lt;p&gt;At some point in the last decade, shipping a web application started requiring a&lt;br&gt;
build step. Then a second one. Then a config file for the bundler, a config file for&lt;br&gt;
the CSS processor, a config file for the linter that runs before the bundler, and a&lt;br&gt;
CI definition that orchestrates all of them in the right order.&lt;/p&gt;

&lt;p&gt;Every one of those steps is a dependency. Every dependency has a version. Every version&lt;br&gt;
will, eventually, break something.&lt;/p&gt;

&lt;p&gt;When I started building Smeldr, I made a deliberate choice: no build pipeline. Not&lt;br&gt;
"we will add it later." Not "we use a lightweight one." None. The build tool is&lt;br&gt;
&lt;code&gt;go build&lt;/code&gt;. The output is a single binary. That is the whole pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  KISS, taken seriously
&lt;/h2&gt;

&lt;p&gt;Keep It Simple is easy to say. It is harder to actually do when the ecosystem keeps&lt;br&gt;
offering you tools that each solve a real problem. Webpack solves module bundling.&lt;br&gt;
PostCSS solves CSS compatibility. PurgeCSS solves stylesheet size. Each is reasonable&lt;br&gt;
on its own. Together they form a chain where a breaking change in one link stops&lt;br&gt;
deployment.&lt;/p&gt;

&lt;p&gt;Simplicity is not about being clever. It is about counting the things that can go&lt;br&gt;
wrong and removing as many as you can. A pipeline with five steps has five places to&lt;br&gt;
fail. A pipeline with zero steps has zero.&lt;/p&gt;

&lt;p&gt;The question I kept asking was: does this tool solve a problem I actually have, or a&lt;br&gt;
problem the tool introduced by existing?&lt;/p&gt;




&lt;h2&gt;
  
  
  No vendor lock-in
&lt;/h2&gt;

&lt;p&gt;Build pipelines accumulate platform-specific config. A Vercel deployment looks&lt;br&gt;
different from a Fly.io deployment. A Netlify function is not a plain HTTP handler.&lt;br&gt;
Before long, the pipeline is not just a build tool, it is an implicit contract with&lt;br&gt;
a specific platform.&lt;/p&gt;

&lt;p&gt;A Go binary does not care where it runs. Linux on a VPS, a container on any&lt;br&gt;
orchestrator, a bare metal server in a data center. No platform-specific config.&lt;br&gt;
No adapter layer. The deployment target is: somewhere that can run a process.&lt;/p&gt;

&lt;p&gt;That is a decision you can change later without rewriting anything.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deployment risk
&lt;/h2&gt;

&lt;p&gt;The riskiest moment in any software project is deployment. Something that worked&lt;br&gt;
locally is moving into production. The fewer differences between those two environments,&lt;br&gt;
the lower the risk.&lt;/p&gt;

&lt;p&gt;A build pipeline is a set of differences. The bundler version on your laptop versus&lt;br&gt;
CI versus the version from six months ago when the last deployment happened. The&lt;br&gt;
environment variables that PostCSS needs to find its config. The Node version that&lt;br&gt;
the build tooling requires, quietly different from the one in production.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;go build&lt;/code&gt;, the binary that runs on your laptop is the binary that runs in&lt;br&gt;
production. There is no transformation step that can introduce a discrepancy. You&lt;br&gt;
test the artifact, you ship the artifact.&lt;/p&gt;




&lt;h2&gt;
  
  
  A deferred cost
&lt;/h2&gt;

&lt;p&gt;A build pipeline is often justified as a time saver. Configure the bundler once,&lt;br&gt;
and development goes faster.&lt;/p&gt;

&lt;p&gt;That framing is mostly correct. The bundler does save time on the specific problems&lt;br&gt;
it was designed to solve. The time it costs shows up later, distributed across every&lt;br&gt;
upgrade cycle, every dependency audit, every CI failure that turns out to be a version&lt;br&gt;
mismatch between the bundler and a plugin.&lt;/p&gt;

&lt;p&gt;You are not eliminating the cost. You are deferring it, and adding interest.&lt;/p&gt;

&lt;p&gt;The pipeline was supposed to give you time back. For some software it does. For a&lt;br&gt;
lot of software it turns out to borrow that time from your future self instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;There are real trade-offs. No build pipeline means no tree-shaking for JavaScript, no&lt;br&gt;
CSS minification by default, no hot module replacement during development.&lt;/p&gt;

&lt;p&gt;The boundary is not "backend versus frontend." smeldr.dev has frontend JavaScript and&lt;br&gt;
still ships without a bundler. The boundary is further out than people assume: it is&lt;br&gt;
the point where you are building a large interactive single-page application whose&lt;br&gt;
JavaScript genuinely benefits from tree-shaking, code splitting, and hot module&lt;br&gt;
replacement. Static assets served over standard HTTP, and CSS served as-is, do not&lt;br&gt;
need any of that.&lt;/p&gt;

&lt;p&gt;The honest version: if you are building a JavaScript-heavy single-page application,&lt;br&gt;
you need a bundler and you should have one. The build pipeline exists because it solves&lt;br&gt;
real problems for a specific class of software.&lt;/p&gt;

&lt;p&gt;The question is whether your software is in that class, or whether you acquired a&lt;br&gt;
pipeline because that is what software acquires.&lt;/p&gt;




&lt;h2&gt;
  
  
  go build is the pipeline
&lt;/h2&gt;

&lt;p&gt;Smeldr compiles with &lt;code&gt;go build&lt;/code&gt;. The binary embeds templates and static assets via&lt;br&gt;
&lt;code&gt;embed.FS&lt;/code&gt;. There is no separate asset pipeline, no manifest, no fingerprinting step.&lt;br&gt;
The binary is self-contained.&lt;/p&gt;

&lt;p&gt;This is not a backend-only claim. The project's site, smeldr.dev, runs on Smeldr. It&lt;br&gt;
has interactive frontend features and no build pipeline. Two categories of JavaScript,&lt;br&gt;
neither of which requires a bundler.&lt;/p&gt;

&lt;p&gt;The first is a third-party 3D animation: 110k particles, WebGL2, a file too large&lt;br&gt;
and complex to write inline. That one is vendored and served as a static file with&lt;br&gt;
&lt;code&gt;&amp;lt;script src defer&amp;gt;&lt;/code&gt;. One HTTP request, no transformation.&lt;/p&gt;

&lt;p&gt;The second is everything else: navigation toggle, copy buttons, devlog tag filtering,&lt;br&gt;
a mobile drawer, dark mode with localStorage persistence. These live as plain&lt;br&gt;
JavaScript inside Go templates. They embed into the binary with the same &lt;code&gt;embed.FS&lt;/code&gt;&lt;br&gt;
that embeds the HTML. No separate asset pipeline, no manifest. The compiler includes&lt;br&gt;
them the same way it includes everything else.&lt;/p&gt;

&lt;p&gt;Two different categories of frontend code. The same answer for both.&lt;/p&gt;

&lt;p&gt;Deployment is: copy binary, restart process. Or build a container image in one&lt;br&gt;
Dockerfile stage. Or use &lt;code&gt;fly deploy&lt;/code&gt;. The platform does not matter because the binary&lt;br&gt;
makes no assumptions about it.&lt;/p&gt;

&lt;p&gt;When a dependency updates, &lt;code&gt;go get&lt;/code&gt; and &lt;code&gt;go build&lt;/code&gt; tell you immediately if something&lt;br&gt;
broke. There is no intermediate tool to negotiate with. The compiler is the build&lt;br&gt;
system, and it has been maintained by Google since 2009.&lt;/p&gt;




&lt;p&gt;The shoulder tension disappears when you do not need most of the pipeline. Not&lt;br&gt;
because pipelines are bad engineering, but because the best tool for the job is often&lt;br&gt;
the one you do not have to configure, maintain, and debug at 2am.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://smeldr.dev?utm_source=devto&amp;amp;utm_campaign=no-build-pipeline" rel="noopener noreferrer"&gt;Smeldr&lt;/a&gt; is open source, written in Go, no build pipeline required.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>webdev</category>
      <category>go</category>
      <category>programming</category>
    </item>
    <item>
      <title>The AI era needs typed data, states, and relations. CMSes almost got there first.</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Fri, 26 Jun 2026 11:32:14 +0000</pubDate>
      <link>https://dev.to/smeldr/the-ai-era-needs-typed-data-states-and-relations-cmses-almost-got-there-first-3m41</link>
      <guid>https://dev.to/smeldr/the-ai-era-needs-typed-data-states-and-relations-cmses-almost-got-there-first-3m41</guid>
      <description>&lt;p&gt;The content management systems of the 2000s and 2010s solved a real problem. They gave&lt;br&gt;
content a shape: defined types, structured fields, lifecycle states. A blog post was&lt;br&gt;
not just a row in a table. It was a thing with a title, a body, a status, a&lt;br&gt;
publication date. It could be in Draft or Published or Archived. You could list all&lt;br&gt;
published posts. You could prevent a draft from being served to readers.&lt;/p&gt;

&lt;p&gt;This was good thinking. Typed data and explicit states make software more correct and&lt;br&gt;
more predictable. The CMS world figured that out early.&lt;/p&gt;

&lt;p&gt;But CMSes were built for one kind of operator: a human working through a browser. The&lt;br&gt;
API was an afterthought. Relations between content items were baked into the UI or&lt;br&gt;
bolted on as plugins. The whole system assumed a person was making the decisions.&lt;/p&gt;

&lt;p&gt;That assumption is changing. I started thinking about this in February, and started&lt;br&gt;
building.&lt;/p&gt;




&lt;p&gt;The premise was simple: agents need the same things CMSes were built around, typed&lt;br&gt;
data, explicit states, relations. But through a protocol, not a UI, and with enforcement, not just convention. An agent that talks to a typed content layer knows&lt;br&gt;
what fields exist, knows what state transitions are valid, and gets a typed error when&lt;br&gt;
it tries something illegal. The lifecycle is not in the prompt. It is in the layer.&lt;/p&gt;




&lt;p&gt;Here is what that looks like in practice. An agent calls preview_impact before archiving a source document:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tool: preview_impact&lt;br&gt;
target_type: source&lt;br&gt;
target_id: "src-42"&lt;br&gt;
→ { "count": 4, "dependents": [...] }&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The agent sees that four published posts cite this source before it acts. That is not a prompt instruction. It is a query against the typed relation graph.&lt;/p&gt;

&lt;p&gt;State transitions are enforced the same way. Calling publish_post on an Archived item returns a typed error: invalid state transition. The agent does not need to be told this in the prompt. The layer refuses.&lt;/p&gt;

&lt;p&gt;Custom state machines, where you define your own lifecycle stages like Draft, Review, Approved, Published, are on the roadmap. The built-in states are the foundation that makes them possible.&lt;/p&gt;




&lt;p&gt;I had been building this for a couple of months when Andrej Karpathy posted about his&lt;br&gt;
LLM Wiki. His pattern: instead of running RAG over documents every time, have an agent&lt;br&gt;
build and maintain a persistent wiki of structured, interlinked markdown files that&lt;br&gt;
compounds over time. His framing was: "Obsidian is the IDE. The LLM is the programmer.&lt;br&gt;
The wiki is the codebase."&lt;/p&gt;

&lt;p&gt;It was a good confirmation that the need is real. Agents need persistent, structured,&lt;br&gt;
queryable knowledge. Not a raw database. Not a chat history. Something shaped.&lt;/p&gt;

&lt;p&gt;But it also showed clearly where markdown stops. There is no enforcement in a markdown&lt;br&gt;
file. There is no state machine. There is no way to say "this decision is Active and&lt;br&gt;
blocks these goals" and have that mean something to the agent beyond text it was told.&lt;br&gt;
Relations are backlinks, great for a human reading a graph view, but not a typed edge&lt;br&gt;
a system can query and act on. And it is local, one developer, one machine.&lt;/p&gt;




&lt;p&gt;What if you took those same ideas and built them with enforcement instead of convention?&lt;/p&gt;

&lt;p&gt;Typed content means the agent knows what fields exist and what they accept. Explicit&lt;br&gt;
state machines mean invalid transitions are rejected at the protocol level: an agent&lt;br&gt;
cannot publish a draft that has not been through review, because the framework refuses&lt;br&gt;
the transition, not because the prompt told it not to. Typed relations between items&lt;br&gt;
form a persistent edge graph the agent can query: what depends on this item, what&lt;br&gt;
decisions are related to this goal, what jobs produced this output.&lt;/p&gt;

&lt;p&gt;The access layer is MCP. The agent calls tools, not endpoints. The tools carry the&lt;br&gt;
shape and the constraints.&lt;/p&gt;




&lt;p&gt;A few things this is not. Graphiti (from Zep) is a temporal knowledge graph that extracts entities and relations from conversational episodes using an LLM. Good for agent memory derived from unstructured input, built on Neo4j. LangGraph orchestrates how an agent reasons, not what it operates on. New MCP servers expose APIs, they do not enforce state machines.&lt;/p&gt;

&lt;p&gt;The distinction that matters: Graphiti derives structure. Smeldr enforces it. If you need to extract a graph from past conversations, Graphiti is the right tool. If you need a typed, stateful layer that an agent operates through, with transitions that are rejected at the protocol level rather than in a prompt, that is a different problem.&lt;/p&gt;

&lt;p&gt;These are complements for most systems, not competitors.&lt;/p&gt;




&lt;p&gt;This is where the use cases go beyond content management.&lt;/p&gt;

&lt;p&gt;Relations and edges can model "this article cites this source," but also "this decision&lt;br&gt;
blocks this goal" or "this agent job produced this output." The same primitives that&lt;br&gt;
power a content graph can power orchestration, memory, context management. An agent&lt;br&gt;
that starts a session can query the graph for active goals, related constraints, and&lt;br&gt;
decisions that are still live. It works within a structure that was built for it to&lt;br&gt;
reason over, not a structure it has to interpret from prose.&lt;/p&gt;

&lt;p&gt;CMSes touched this space and stopped at the browser. Karpathy's wiki takes it further&lt;br&gt;
but stays in flat files. The next step is typed edges with enforced lifecycle, queryable&lt;br&gt;
by agents through a protocol designed for them.&lt;/p&gt;




&lt;p&gt;That is what I am building with Smeldr: an open source Go framework where content&lt;br&gt;
types have explicit lifecycle states, relations between items are typed edges in a&lt;br&gt;
persistent graph, and agents interact through MCP tools that enforce the rules rather&lt;br&gt;
than expose raw data.&lt;/p&gt;

&lt;p&gt;It started as a content backend. The further I get into it, the more it looks like&lt;br&gt;
something more general: a typed, stateful graph that agents can safely operate a whole&lt;br&gt;
system through.&lt;/p&gt;

&lt;p&gt;The CMS people were onto something. Karpathy is onto something. I think the next piece&lt;br&gt;
is putting enforcement and typed relations underneath it.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://smeldr.dev" rel="noopener noreferrer"&gt;Smeldr&lt;/a&gt; is open source, written in Go, no build pipeline required.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>go</category>
    </item>
    <item>
      <title>Anthropic described the right way to build MCP servers. Gut feeling confirmed.</title>
      <dc:creator>Peter</dc:creator>
      <pubDate>Wed, 17 Jun 2026 09:45:14 +0000</pubDate>
      <link>https://dev.to/smeldr/anthropic-described-the-right-way-to-build-mcp-servers-gut-feeling-confirmed-38gc</link>
      <guid>https://dev.to/smeldr/anthropic-described-the-right-way-to-build-mcp-servers-gut-feeling-confirmed-38gc</guid>
      <description>&lt;p&gt;I make a lot of design decisions on gut feeling. Is this the right way to structure MCP tools? Should the transport be remote or local? How much context should an agent get about the fields it's writing to?&lt;/p&gt;

&lt;p&gt;You make your best call, ship it, and move on.&lt;/p&gt;

&lt;p&gt;About six weeks ago, Anthropic published their recommendations for production-ready MCP servers. I read through it and felt that quiet satisfaction you get when someone else independently arrives at the same place. Every single point mapped to something already in Smeldr.&lt;/p&gt;

&lt;p&gt;Here's how:&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Build remote servers, not local/stdio
&lt;/h2&gt;

&lt;p&gt;Anthropic's recommendation: deploy servers that run remotely. Local stdio-only servers can't reach web, mobile, or cloud-hosted agents.&lt;/p&gt;

&lt;p&gt;Smeldr ships with HTTP+SSE transport. The MCP server runs as a standard HTTP server. An agent running anywhere connects over the network with a bearer token.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Group tools around intent, not raw endpoints
&lt;/h2&gt;

&lt;p&gt;Anthropic's recommendation: fewer tools focused on what users want to accomplish, not mirrored one-to-one from your API surface.&lt;/p&gt;

&lt;p&gt;Smeldr generates tools grouped around content lifecycle intent. For a &lt;code&gt;Story&lt;/code&gt; type you get &lt;code&gt;create_story&lt;/code&gt;, &lt;code&gt;update_story&lt;/code&gt;, &lt;code&gt;publish_story&lt;/code&gt;, &lt;code&gt;archive_story&lt;/code&gt;, &lt;code&gt;list_stories&lt;/code&gt;, &lt;code&gt;get_story&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The agent doesn't need to know about routes, database rows, or state transitions. It calls &lt;code&gt;publish_story&lt;/code&gt; and the framework handles the rest: lifecycle validation, 404 enforcement for non-published content, sitemap update.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Ship rich tool semantics
&lt;/h2&gt;

&lt;p&gt;Anthropic's recommendation: use MCP's richer features to give agents better context about what tools do and what fields mean.&lt;/p&gt;

&lt;p&gt;Smeldr adds semantics at the struct level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Story&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;smeldr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`smeldr:"required,min=3" smeldr_description:"The headline of the story"`&lt;/span&gt;
    &lt;span class="n"&gt;Excerpt&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`smeldr:"required"       smeldr_description:"1-2 sentences used in listing and as meta description"`&lt;/span&gt;
    &lt;span class="n"&gt;Body&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`smeldr:"required"       smeldr_format:"markdown"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;smeldr_description tells the agent what a field is for. smeldr_format tells it what format to use. These are baked into the MCP schema generated from the struct. Every tool call carries this context automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Standardised authentication
&lt;/h2&gt;

&lt;p&gt;Anthropic's recommendation: implement standardised auth to avoid unexpected re-authentication and enable fast first-time setup.&lt;/p&gt;

&lt;p&gt;Smeldr uses BearerHMAC tokens. Every MCP call is authenticated with a signed bearer token. Token management is available via MCP tools, so an agent with the right role can provision access for other agents.&lt;/p&gt;

&lt;p&gt;OAuth 2.0 is live - Claude.ai and ChatGPT connections both verified. For self-hosted deployments, token-based auth gets you running in minutes.&lt;/p&gt;




&lt;p&gt;That's the whole list. The problem made these answers obvious. It's always good when the spec agrees.&lt;/p&gt;

&lt;p&gt;Originally published on &lt;a href="https://smeldr.dev/devlog/anthropic-validation?utm_source=devto&amp;amp;utm_campaign=anthropic-validation" rel="noopener noreferrer"&gt;smeldr.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>ai</category>
      <category>mcp</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
