Building a TypeScript framework that agents can actually reason

Sam51 Novice 1h ago 566 views 14 likes 2 min read

I've been watching agents write the same boilerplate across Hono, Drizzle, Zod, and Inertia for months. Every project wires them differently. Every agent hallucinates slightly different conventions. So I built Guren — a fullstack framework for Bun that bakes those decisions in upfront, then gives the agent (and me) a mechanical way to verify what came back.

The stack isn't novel: Hono for HTTP, Drizzle for ORM, Zod for validation, Inertia + React + Vite for the frontend. What's different is the convention layer. A feature is a route, controller, model, and view — authentication, queues, mail, scheduling, and policies are already wired before you write a line.

Controllers follow the shape you'd expect

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')
  }
}

Batteries 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 schema on the server and the form's type follows. Rename the route and route('posts.store') stops compiling. Page props are checked against the component's own Props interface — a misspelled prop is a typecheck failure, not a blank space on the page.

The evaluation that changed the roadmap

I ran a cost evaluation across frameworks to see what a feature actually costs an agent. Expected Guren to win because decisions are pre-made. First measurement: $5.54 median, 2.7× Hono's cost.

Stream analysis explained it. Agents spent 17–46% of all tool actions reverse-engineering @guren internals — reading generated types, jumping through convention files, reconstructing mental models that the framework should have made explicit.

That's the real problem. A framework designed for agents needs to be legible to agents, not just convenient for humans. The conventions that save me keystrokes were costing the agent thousands of tokens in exploration.

What I'm changing

  • Generated .gen files now ship with JSDoc comments that explain the convention, not just the shape
  • Route registration emits a machine-readable manifest the agent can query directly
  • Controller base classes expose their capabilities via static analysis hints
  • Validation schemas are co-located with route definitions in a way the agent can trace without grep

The goal: an agent that opens the repo, reads the manifest, and writes correct code on the first pass — no 40-turn exploration loops.

Guren is at guren.dev and github.com/gurenjs/guren if you want to see the current shape. Still early, but the direction feels right: treat agent legibility as a first-class design constraint, not an afterthought.

webdevtypescriptAI ProgrammingAI Coding

All Replies (3)

Z
Zoe12 Novice 1h ago
Missing: shared TypeScript configs that enforce the wiring patterns so agents can't drift per-project.
0 Reply
M
MicroPanda Intermediate 1h ago
How do you handle schema drift between Drizzle and Zod at runtime?
0 Reply
G
GhostFounder Intermediate 58m ago
Had an agent invent its own validation layer last week — completely ignored the Zod schemas I'd already defined.
0 Reply

Write a Reply

Markdown supported