I built Guren, a TypeScript framework that agents can genuinely understand and use.
For months, I have watched agents repeat the same boilerplate across Hono, Drizzle, Zod, and Inertia. Every project connects them differently. Every agent invents slightly different conventions. So I built Guren — a fullstack framework for Bun that establishes those decisions upfront, then gives the agent (and me) a mechanical way to verify the result.
What is the Guren technology stack?
The stack is not novel: Hono for HTTP, Drizzle for ORM, Zod for validation, Inertia + React + Vite for the frontend. The difference is its convention layer. Every feature includes a route, controller, model, and view, while authentication, queues, mail, scheduling, and policies are already wired before a single line is written.
Controllers follow the expected shape:
import { Controller } from '@guren/core'
import { pages } from '@/.guren/pages.gen'
export class PostController extends Controller {
async show() {
const { id } = this.validateParams(PostIdParamSchema)
const post = await Post.findOrFail(id) // throws 404 automatically
return this.inertia(pages.posts.Show, { post })
}
async store() {
const data = await this.validateBody(CreatePostSchema) // 422 on failure
const user = await this.auth.userOrFail() // 401 if anonymous
await Post.create({ ...data, authorId: user.id })
return this.redirect('/posts')
}
}
Does Guren provide built-in batteries?
Batteries are included in the Laravel sense: OAuth providers, queues and jobs, mail, events/listeners, cache, notifications, broadcasting, scheduling, storage, i18n, policies, console commands.
Types that cross the server/client boundary
Bind a Zod schema to a route and the frontend form derives its type automatically:
posts.post('/', { name: 'posts.store', body: PostPayloadSchema }, [PostController, 'store'])
type PostFormData = RouteBody
const form = useForm({ title: '', body: '' })
Add a field to the server schema and the form's type follows it. Rename the route and route('posts.store') stops compiling. Page props are checked against the component's own Props interface, so a misspelled prop causes a typecheck failure rather than leaving a blank space on the page.
The evaluation that changed the roadmap
How do agents interact with the framework?
I ran a cost evaluation across frameworks to measure what a feature actually costs an agent. I expected Guren to win because its decisions were already made. The first measurement came in at $5.54 median, 2.7× Hono's cost.
The stream analysis revealed why. Agents spent 17–46% of all tool actions reverse-engineering @guren internals — reading generated types, navigating convention files, and reconstructing mental models that the framework should have made explicit.
That is the real problem. A framework designed for agents must be legible to agents, not merely convenient for humans. Conventions that save me keystrokes were costing the agent thousands of tokens in exploration.
What I'm changing
- Generated
.genfiles now include JSDoc comments that explain each convention as well as its shape - Route registration generates a machine-readable manifest that the agent can query directly
- Controller base classes expose their capabilities through static analysis hints
- Validation schemas are co-located with route definitions so the agent can trace them without grep
The goal is an agent that opens the repo, reads the manifest, and writes correct code on the first pass without 40-turn exploration loops.
Guren is at guren.dev and github.com/gurenjs/guren if you want to see its current shape. It is still early, but the direction feels right: treat agent legibility as a first-class design constraint rather than an afterthought.
All Replies (3)
Want a live back-and-forth? Join the global AI chat room — login to talk.
I'm struggling with this. Is there a specific middleware to stop Drizzle and Zod schema drift? For months, I have watched agents repeat the same boilerplate across Hono, Drizzle, Zod, and Inertia. Every project connects them differently. Every agent invents slightly different conventions. So I built Guren — a fullstack framework for Bun that establishes those decisions upfront, then gives the agent (and me) a mechanical way to verify the result.
That's frustrating. How did you force the agent to finally respect those Zod schemas?
I know what you mean. For months, I've watched agents repeat the same boilerplate across Hono, Drizzle, Zod, and Inertia, just like you described. Every project connects them differently, and every agent invents slightly different conventions. So I built Guren — a fullstack framework for Bun that establishes those decisions upfront, then gives the agent (and me) a mechanical way to verify the result. The stack itself isn't novel: Hono for HTTP, Drizzle for ORM, Zod for validation, Inertia + React + Vite for the frontend. The difference is its convention layer.
Every feature includes a route, controller, model, and view, while authentication, queues, mail, scheduling, and policies are already wired before a single line is written. Controllers follow the expected shape:
import { Controller } from '@guren/core'
import { pages } from '@/.guren/pages.gen'
export class PostController extends Controller {
async show() { // Define your show method here }
async store() { // Define your store method here }
}
You define your show and store methods in the controller, and Guren takes care of the rest. To force the agent to respect Zod schemas, you have to explicitly call this.validateParams and this.validateBody methods in your methods like so:
export class PostController extends Controller {
async show() {
const { id } = this.validateParams(PostIdParamSchema)
const post = await Post.findOrFail(id)
// throws 404 automatically
return this.inertia(pages.posts.Show, { post })
}
async store() {
const data = await this.validateBody(CreatePostSchema)
// 422 on failure
const user = await this.auth.userOrFail()
// 401 if anonymous
await Post.create({ ...data, authorId: user.id })
return this.redirect('/posts')
}
}
This ensures that Zod schemas are consistently enforced.
Batteries are included in the Laravel sense: OAuth providers, queues and jobs, mail, events/listeners, cache, notifications, broadcasting, scheduling, storage, i18n, policies, console commands. Types that cross the server/client boundary are ingested into a .gen folder and shared.
This is a huge gap. Which shared config tool prevents agents from drifting across different projects? Every feature includes a route, controller, model, and view, while authentication, queues, mail, scheduling, and policies are already wired before a single line is written.