+
+
+
+
diff --git a/packages/posts/src/content.config.ts b/packages/posts/src/content.config.ts
new file mode 100644
index 00000000000..6f8f9166b90
--- /dev/null
+++ b/packages/posts/src/content.config.ts
@@ -0,0 +1,14 @@
+import { defineCollection } from "astro:content"
+import { glob } from "astro/loaders"
+import { z } from "astro/zod"
+
+const posts = defineCollection({
+ loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/posts" }),
+ schema: z.object({
+ title: z.string(),
+ description: z.string(),
+ published: z.coerce.date(),
+ }),
+})
+
+export const collections = { posts }
diff --git a/packages/posts/src/content/posts/hello.mdx b/packages/posts/src/content/posts/hello.mdx
new file mode 100644
index 00000000000..9a480298b32
--- /dev/null
+++ b/packages/posts/src/content/posts/hello.mdx
@@ -0,0 +1,17 @@
+---
+title: "Example one"
+description: "Markdown for the words, components for everything else."
+published: 2026-08-20
+---
+
+import Counter from "../../components/Counter.astro"
+
+This is a post written in MDX. Most of it is ordinary Markdown, rendered to static HTML with no client-side JavaScript.
+
+## Interactive when it matters
+
+Components can appear directly between paragraphs. This one is an Astro component with a tiny browser script:
+
+
+
+The component could also come from another workspace package or use React, Solid, Svelte, or Vue when the interaction calls for it.
diff --git a/packages/posts/src/content/posts/inside-the-loop.mdx b/packages/posts/src/content/posts/inside-the-loop.mdx
new file mode 100644
index 00000000000..976e3c2b5d9
--- /dev/null
+++ b/packages/posts/src/content/posts/inside-the-loop.mdx
@@ -0,0 +1,9 @@
+---
+title: "Lorem ipsum"
+description: "A closer look at what happens between a prompt and a result."
+published: 2026-08-07
+---
+
+An agent alternates between reasoning, acting, and observing. The useful part is not any single step, but how quickly the loop incorporates new information.
+
+Interactive explanations let us inspect that process instead of reducing it to a static diagram.
diff --git a/packages/posts/src/content/posts/tools-should-disappear.mdx b/packages/posts/src/content/posts/tools-should-disappear.mdx
new file mode 100644
index 00000000000..cda666bb904
--- /dev/null
+++ b/packages/posts/src/content/posts/tools-should-disappear.mdx
@@ -0,0 +1,9 @@
+---
+title: "Example two"
+description: "The best interface gets out of the way of the work."
+published: 2026-08-14
+---
+
+Software should make difficult work feel direct. Every control has to earn the attention it asks for.
+
+The goal is not fewer capabilities. It is less distance between intent and result.
diff --git a/packages/posts/src/layouts/Layout.astro b/packages/posts/src/layouts/Layout.astro
new file mode 100644
index 00000000000..3e253e1e75e
--- /dev/null
+++ b/packages/posts/src/layouts/Layout.astro
@@ -0,0 +1,32 @@
+---
+import "@fontsource/commit-mono/400.css"
+import "@fontsource/commit-mono/500.css"
+import "../styles/global.css"
+
+interface Props {
+ title?: string
+ description?: string
+}
+
+const title = Astro.props.title ? `${Astro.props.title} | OpenCode Posts` : "OpenCode Posts"
+const description = Astro.props.description ?? "Notes from the OpenCode team."
+---
+
+
+
+
+
+
+
+
+ {title}
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/posts/src/pages/[...slug].astro b/packages/posts/src/pages/[...slug].astro
new file mode 100644
index 00000000000..c1bc6305f34
--- /dev/null
+++ b/packages/posts/src/pages/[...slug].astro
@@ -0,0 +1,27 @@
+---
+import { getCollection, render } from "astro:content"
+import Layout from "../layouts/Layout.astro"
+
+export const prerender = true
+
+export async function getStaticPaths() {
+ return (await getCollection("posts")).map((post) => ({ params: { slug: post.id }, props: { post } }))
+}
+
+const post = Astro.props.post
+const rendered = await render(post)
+const Content = rendered.Content
+---
+
+
+
+