DEVLOG

What we ship. Why we ship it.

A running log of decisions, releases, and architecture notes from building Smeldr.

trace_lineage: walking a decision back to what it actually rests on

Decisions build on decisions. When an agent doubts something three hops downstream, tracing that doubt back to its actual premise means walking the chain, not guessing at it. That's RelationStore.TraceLineage now.

Decision authority wasn't missing. It was three unset switches.

Nothing about ratifying or superseding a governance Decision required new capability. The role/grant system, the per-transition role gate, the state machine — all of it already existed and already worked. It was just never switched on for these two specific transitions, and the switch itself had three ways to silently do nothing when flipped.

A pattern that looked identical in two places — and worked in only one

Two call sites, one helper function, the same three-line pattern for recovering an authenticated actor from a context.Context. Copy the pattern from the place it already works, apply it to the place it's missing, done — except the second call site silently never worked, and the only way to find that out was to write the test the plan called for and watch it fail.

Devlog: The PUT That Bypassed Everything (A217)

We just closed a quiet but significant gap in Smeldr's state governance layer. The fix is small — seven lines — but the gap it closes was hiding behind a test that looked correct but wasn't: HTTP PUT requests could change a content item's status to anything, bypassing every transition and role check.

Closing the orchestration state-validation gap

When we ran the T147 data migration, we discovered that 14 amendments had been created with status="done" — a state that exists in the goal and task flows but not in the amendment flow. The MCP create_amendment tool accepted the call without error. The root cause: Smeldr validated state transitions, but never the initial state at create time.

Closing the gaps in dynamic content: validation, scheduling, and AI indexing

When we shipped the dynamic content substrate in v1.41.0, DynamicTypeRepo accepted any map[string]any you handed it — no validation against the registered schema. This release closes those gaps: field validation, scheduling support, and AI index registration at boot.

Decision Freshness: Scheduled State Re-evaluation in Smeldr v1.47.0

Smeldr v1.47.0 ships TransitionTrigger and App.DrainEvalQueue — a lightweight mechanism for scheduling automatic state transitions in the future, with no cron-at-definition-time complexity.

State flow tools land in smeldr.dev/mcp

smeldr.dev/mcp v1.24.0 ships three new tools that expose the T23 custom state flow infrastructure to MCP clients.

Content Relations in MCP: six new tools

The relation graph tools are now wired in smeldr.dev/mcp. If your app calls app.Relations(store), six tools become available automatically — no new server option needed.

Per-path SEO overrides without touching the content model

Content-type list pages have always had an SEO problem: the framework could not know what title or description to put on /posts or /essays because those pages have no single corresponding item. The answer was a per-path override table, slotted into the existing Head fallback chain without touching a single item type.

Renaming the wire without breaking it

Renaming a framework is easy until you reach the wire — the HMAC-signed webhook headers a receiver verifies by name, the resource URIs an agent has already cached, the env vars in someone's CI. Forge became Smeldr at the wire level in v1.32.0 without breaking a single integration, by dual-emitting the new and legacy identifiers with identical values for a deprecation window. Here is the asymmetric-risk reasoning behind it.

Content Relations, Layer 3: SweepStructural

Reactive signals handle the common case. They do not handle bulk imports, direct database writes, or anything that happened while the server was down. SweepStructural is the periodic walk that closes those gaps: an integrity check that runs on a cron schedule, stamps stale edges, and fires the same AfterRelationCascade signal so handlers do not need to know how the staleness was detected.

Content Relations, Layer 2: AfterRelationCascade

When a referenced item is archived, everything that depends on it needs to know. AfterRelationCascade is the signal that carries that notification, fired once per dependent item and debounced 500ms, with three guards that prevent cascade storms. Here is exactly what happens when a target changes state.

Content Relations, Layer 1: the typed edge graph

Content items have always referenced each other. A blog post cites a source, a product page links a spec. Smeldr now tracks those references as a typed edge graph: persisted, queryable, and maintained correct over time by three cooperating layers. Here is how the graph is structured and why three layers are necessary.

No pipeline, no oil spills

Shipping a web app slowly accumulated build steps, and every step is a dependency that will eventually break something. Smeldr made the opposite choice: no build pipeline. The build tool is go build, the output is a single binary, and smeldr.dev runs in production exactly that way, 3D hero animation and all.

The AI era needs typed data, states, and relations. CMSes almost got there first.

The CMS world figured out typed data and explicit states early. But they built it for humans in browsers. Agents need the same primitives: typed fields, state machines, relation graphs. Through a protocol, not a UI, and with enforcement, not just convention.

Operator-controlled URLs for runtime content types

Runtime-defined content types ship public URLs the operator controls, not a framework convention. A patch to T104 Inc 2 removes a Go 1.22 mux conflict and decouples admin routes from the public URL surface.

Your content as a block parent: typed schemas for AI agents

Smeldr v1.38.0 lets your posts, stories, and doc pages host structured block sections, and gives AI agents typed schemas so they know exactly what fields each block type expects. Two capabilities that make the block system useful in real applications.

A homepage that shows its own philosophy

The smeldr.dev homepage now opens with 110,000 GPU-rendered embers: ember dust that drifts, self-organises into a constellation network, and gets swept by a single hard-edged scan before the loop begins again. No bundler, no npm, no Three.js. One vanilla WebGL2 file. Here is why the visual exists, and why the constraint was the right one.

The log capture that deadlocked itself

A small, boring feature — a plain HTTP endpoint to read recent log output — triggered a deadlock on the very first warning in a default Smeldr app. Here is the slog re-entrancy trap that caused it, and the one-line detection that prevents it from coming back.

APIOnly() — content types without a public URL

Not every content type belongs on the public web. APIOnly() is a single module option that makes a content type invisible to browsers while keeping every MCP tool and CLI command intact. No HTML route, no JSON leak, no crawlable admin surface.

Redirects an agent can manage while the server runs

Renaming a URL used to mean a 404 and lost inbound links. Smeldr v1.34.0 adds redirects as a first-class concept: a compile-time option for known renames, and a runtime, database-backed table an AI agent or operator manages live over MCP — 301 or 410, exact or whole-prefix, with redirect chains auto-collapsed. Zero overhead on requests that match a real route.

OAuth 2.1: connect your Smeldr site to ChatGPT and Claude

The MCP specification requires OAuth 2.1 for remote connections. smeldr.dev/oauth ships a full authorization server — authorization code flow, PKCE, refresh tokens, and revocation — wired into your site in four lines. Here is what the spec requires, what we chose not to build, and why ChatGPT works today while Claude.ai does not.

Create new page types without code

Smeldr v1.31.0 lets you define new page types at runtime — no Go struct, no migration, no redeploy. A DynamicNode is a content type whose type is a string. Blocks are the composable pieces that fill it. Your AI agent defines the layout, builds the sections, and publishes the result over MCP.

This is Smeldr

Smeldr is the new name for Forge CMS. Same framework, same architecture, same API. A sharper name for what it has become: a typed, stateful backend your AI agents operate on directly.

Give Your AI the Manual

Your AI does not know Smeldr until you tell it. The complete working reference now ships as skills/smeldr.md: content types, module wiring, every MCP tool and its role, and the things that are easy to get subtly wrong. Drop it in and your agent knows the API before your first message.

Designing Smeldr Pages with AI

Go from a Go struct to production HTML and CSS in one pass, with no markup written by hand. A smeldr-pattern.md file hands an AI design tool your content structure, realistic sample data, and explicit scope, so it builds the page without a live site to look at.

SingleInstance and Standalone - two routing options that were missing

Two routing options that were missing: SingleInstance for singleton pages like a homepage, with no list and no slug, and Standalone for top-level URLs like /my-post instead of /posts/my-post. Routing only, every CRUD operation and MCP tool unchanged.

"The loop: from conversation to scheduled post"

How the Smeldr + Claude setup creates a complete end-to-end developer pipeline from conversation to scheduled social posts - and why the humans are still in the loop at every step.

Who published that? Smeldr now ships an opt-in audit trail

forge v1.22.0 adds App.Audit — a single call that records every publication state transition to a SQL table and exposes it via GET /_audit.

Multi-agent coordination via content lifecycle

Most orchestration frameworks require an upfront workflow definition. Smeldr takes a different approach: agents coordinate through content state transitions. No central orchestrator. No graph. Just a signal bus and a state machine.

Typed persistent state for AI agents, in Go

Most agent frameworks are workflow engines. Smeldr is the state layer those engines operate on: typed, persistent, lifecycle-aware, and exposed via MCP. Here is why that distinction matters.

forge-agent: scheduled and reactive agent jobs in your Smeldr app

forge-agent adds an embedded agent runtime to Smeldr. Agent jobs trigger on content lifecycle signals or cron schedules, and are themselves managed as Smeldr content, with full lifecycle control via MCP.

DB-driven platform config in forge-social

forge-social v0.5.0 moves OAuth app credentials out of environment variables and into the database. One MCP call sets up a platform. No server access required.

X support in forge-social

forge-social v0.5.0 adds X as a third platform. OAuth 2.0 PKCE, 280-character enforcement, automatic token refresh, and image upload in v0.6.0.

Media uploads from anywhere

forge-media v1.2.0 adds a short-lived upload token so agents and browsers can upload files directly without exposing your admin token. The CLI gets direct media commands in the same release.

Draft preview

Share a draft with a client or reviewer before it goes live. Smeldr generates a signed, time-limited URL that bypasses the published-only filter for that one slug.

Set it and forget it: the slot-queue model in forge-social

forge-social's AddRoutes: a concrete example of what the signal bus enables

Smeldr's signal bus is a general extension point. forge-social's AddRoutes is one concrete thing you can build on top of it — outbound HTTP delivery to AI agents.

forge-social: scheduling social posts from your Smeldr instance

forge-social adds social post scheduling directly to your Smeldr instance — no third-party queue, no Zapier. Here's how it works.

From webhooks to a signal bus

Smeldr v1.20.0 ships a signal bus. Your application code can now subscribe to any lifecycle signal with one call. Webhooks are a subscriber. So is your audit log, your cache, your SSE hub.

Outbound webhooks and MCP subscriptions

Smeldr's internal Signals were always there, but nothing outside could react. M11 adds two complementary delivery mechanisms on top of the same Signal: outbound webhooks for pipelines and integrations, MCP subscriptions for agents.

First start

Bootstrap token, static file serving, forge-cli init, and a security fix. What Phase 2 actually shipped.

Iron & Ember

Ember is what stays hot after the fire. Not the flame itself, but the warmth that persists.

Two audiences, one architecture

Building for AI agents sounds like it should add complexity for human developers. Smeldr went the other way. The decisions that make it good for developers are exactly the decisions that make it work for agents.

Smeldr CLI: terminal access to a running instance

forge-cli is the operator tool: terminal access to a running Smeldr instance without opening a browser. The agent has MCP. The operator has the CLI. Same access model, different interface.

Typed MCP tools from a Go struct

forge-mcp generates a complete set of typed MCP tools from every content type you register. This is the operations layer -- the complement to the AI reading formats described in D1.

Three lines and your agent handles files

forge-media adds file management to Smeldr in three lines. Alt text is enforced at the upload boundary from every source, which means images are correctly labelled across all four delivery channels automatically.

Zero dependencies: what it means and why it matters

Smeldr has no third-party Go dependencies. Pure stdlib. The stability this creates is not just philosophical: it has direct consequences for every delivery channel your content serves.

Template infrastructure: partials, assets, and per-request context

App.Partials, HeadAssets, and ContextFunc are the three mechanisms that make it possible to deliver consistent output to all four audiences from a single content definition.

Markdown and trusted HTML in Forge templates

forge_markdown and forge_html are two template functions that cover different rendering needs. The same content pipeline feeds your browser output, your API responses, and your AI-readable formats.

How your AI agent understands your content

forge_description and forge_format are struct tags that bake field semantics directly into the MCP schema. The agent knows what each field means and what format it expects -- without a system prompt.

Four audiences, one framework

Most frameworks serve two audiences: browser and API. Smeldr serves four. The reading layer and the operations layer are complementary, and that is what makes Smeldr AI-native rather than AI-compatible.

Anthropic described the right way to build MCP servers. Here's how Smeldr got there first.

Anthropic published their recommendations for production-ready MCP servers. Smeldr implements every single one: remote transport, intent-grouped tools, field semantics, and standardised auth. The spec caught up to the problem.

Provenance: who did what, and why, without touching the audit trail you already have

Neither of Smeldr's two existing "who did this" mechanisms can actually answer that question everywhere it matters. ProvenanceRecord closes both gaps at once, who, what, and, when supplied, why.

The bug 96% coverage couldn't see: a column type name, not a value

WebhookStore.List shipped, worked in every test, worked in the dogfood instance's ad-hoc checks, and would have hard-failed on the very first call against any database built from its own documented schema. The gap wasn't in the code path everyone reads. It was in a word.

Reachability: a general bounded graph-traversal primitive

Two independent design reviews found the same gap three days apart: nothing could walk Smeldr's relation graph N hops out and report what's found, or genuinely absent, at each ring. RelationStore.Reachability does that now.

How Smeldr wires modules in three lines

Define a struct, embed forge.Node, call app.Content — Smeldr handles routing, storage, feeds, and AI indexing automatically.