{"slug": "my-coding-agent-kept-inventing-columns", "title": "My coding agent kept inventing columns", "summary": "Laravel Truss 1.8, an open-source tool by Alberto Arena, now provides grounding context for coding agents by turning schema exports into annotations, compact exports, and an optional read-only MCP server, preventing agents from inventing columns. The update includes a new 'llm' export format, a fluent Truss facade, and a gated HTTP route, all powered by a single pipeline to ensure consistency.", "body_md": "Laravel\n\n# My coding agent kept inventing columns\n\nLaravel Truss 1.8 turns your schema export into grounding context for a coding agent: annotations, focused and compact exports, and an optional read-only MCP server.\n\n## On this page\n\n## Truss series\n\n- 1\n[There's no artisan schema:show, so I built one](/posts/introducing-truss/) - 2\n[The schema doctor is in](/posts/the-schema-doctor-is-in/) - 3\n[I gave my schema viewer your app's colours](/posts/gave-my-schema-viewer-your-app-colours/) - 4 My coding agent kept inventing columns\n\n[View series →](/series/truss/)\n\nAsk a coding agent to write a query against a table it hasn’t seen this session, and it will guess. Confidently, plausibly, and wrong: a foreign key named `author_id`\n\nwhen the column is actually `created_by`\n\n, a `status`\n\ntreated as a free-text string when it’s a `tinyint`\n\nenum, a table it’s sure exists because a table like it usually does. It isn’t lying, it just doesn’t know, and nothing forces it to say so.\n\nThe usual fix is pasting a schema dump into the chat at the start of a session. That works until the next migration, at which point it’s just a different kind of wrong: confidently out of date instead of confidently invented.\n\nThere’s an obvious third option: let the agent run queries itself, most coding agents can already execute SQL. But that means handing over real database credentials, and a connection that can query can also see rows, not just structure. That’s a bigger grant than the problem needs.\n\n[Truss](https://github.com/albertoarena/laravel-truss) has always been a live, zoomable ER diagram of your Laravel app’s real database schema, structure only, never a row of data. Version 1.8 points that same live structure at a coding agent instead of a browser tab.\n\n## Give it meaning a type can’t carry\n\nA column tells an agent its name and type, not what it means. `status = 1`\n\ndoesn’t say “paid” on its own. Annotate it once and every export carries it:\n\n``` js\n// config/truss.php\n'annotations' => [\n    'source' => ['config', 'database'],\n    'tables' => [\n        'orders' => [\n            'note' => 'One row per checkout attempt, not per completed order.',\n            'columns' => [\n                'status' => 'tinyint: 0 pending, 1 paid, 2 refunded',\n            ],\n        ],\n    ],\n],\n```\n\nIf your database already carries `COMMENT`\n\nstrings on tables and columns, leave `'database'`\n\nin `annotations.source`\n\nand Truss reads those directly, no duplicate config to keep in sync. Either way, a comment is part of the `CREATE TABLE`\n\ndefinition, not a row: still structure only. Strip them from any single export with `--no-annotations`\n\nwhen you just want the bare shape.\n\n## Trim it to what the question needs\n\nA forty-table schema is a lot of tokens to spend on a question about one table. `--compact`\n\ndrops column defaults and non-unique indexes without dropping a single table, column, or foreign key. `--focus=orders --depth=1`\n\nnarrows the export to one table and its foreign-key neighbourhood, the same idea as the dashboard’s focus mode, now available from the command line. And there’s a new `llm`\n\nformat alongside the existing five (DBML, JSON, CSV, Markdown, Mermaid), a dense plaintext export tuned for a token budget rather than for a human reading a data dictionary:\n\n```\nphp artisan truss:export --format=llm --focus=orders --depth=1 --compact\n```\n\nThat’s what the export produces. Calling it is just as direct, in code or over HTTP.\n\nBuilding the same thing in code goes through a new fluent, immutable `Truss`\n\nfacade instead of the command:\n\n``` php\nTruss::snapshot()->focus('orders', depth: 1)->compact()->toDbml();\n```\n\nAnd a gated `GET {prefix}/export/{format}`\n\nroute serves the identical output to any HTTP client, behind the same `viewTruss`\n\ngate as the dashboard. Command, facade, route, dashboard download: one pipeline underneath all four, so they can never quietly disagree with each other.\n\n## Ask it live, instead of pasting a snapshot\n\nThe part I actually wanted, though, wasn’t a better export. It was not exporting anything at all.\n\nTruss 1.8 adds an optional server for MCP, the Model Context Protocol that Claude Code, Claude Desktop, and Cursor use to reach outside tools. Built on `laravel/mcp`\n\n, it talks to a coding agent directly over local stdio:\n\n```\ncomposer require laravel/mcp\nphp artisan mcp:start truss\n```\n\nPoint one at it and the agent gets five tools, `list_tables`\n\n, `describe_table`\n\n, `get_schema`\n\n, `focus_table`\n\n, and `get_structural_review`\n\n, plus a `truss://schema`\n\nresource, all reading the live schema on demand. Every tool advertises MCP’s `readOnlyHint`\n\n, so a client can present them as read-only instead of prompting for write approval on a call that was never going to write anything. No row data, ever, and the same exclusion and managed-connection safeguards as the rest of Truss apply here too, opt-in and off by default behind `truss.mcp.enabled`\n\n.\n\nI pointed it at a real project I’ve worked on for a while, in Claude Desktop, and the difference was immediate: instead of me pasting a schema dump at the start of the conversation, or the agent asking me to run a query to check a column name, it just called `describe_table`\n\nbefore it wrote anything, the same check that would have caught the guessed `author_id`\n\nfrom the start of this post. No staleness, because there’s nothing to go stale, it’s reading the same live introspection the diagram uses.\n\n## Try it\n\n[Live demo](https://trussphp.com/demo/), running against a fictional schema- AI context guide:\n[trussphp.com/guides/ai-context](https://trussphp.com/guides/ai-context/) - MCP server guide:\n[trussphp.com/guides/mcp-server](https://trussphp.com/guides/mcp-server/) - Full changelog:\n[CHANGELOG.md on GitHub](https://github.com/albertoarena/laravel-truss/blob/main/CHANGELOG.md)\n\nUpdate with `composer update albertoarena/laravel-truss`\n\n, and if you want the MCP server too, `composer require laravel/mcp`\n\non top.\n\n## What’s next\n\nMore `truss:doctor`\n\nrules and CI-native output formats are next on the [roadmap](https://trussphp.com/roadmap/), followed by reading Eloquent relationships for semantic edge labels instead of raw foreign keys, and navigation aids for schemas with a hundred tables or more. If a tool the agent needs isn’t there yet, or an annotation source you’d want isn’t supported, [open a discussion](https://github.com/albertoarena/laravel-truss/discussions).\n\n[Open a discussion on GitHub](https://github.com/albertoarena/laravel-truss/discussions)or\n\n[send me an email](mailto:hello@albertoarena.it).", "url": "https://wpnews.pro/news/my-coding-agent-kept-inventing-columns", "canonical_source": "https://albertoarena.it/posts/my-coding-agent-kept-inventing-columns/", "published_at": "2026-08-10 10:00:00+00:00", "updated_at": "2026-08-10 14:09:21.917131+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "ai-agents"], "entities": ["Laravel Truss", "Alberto Arena", "Truss"], "alternates": {"html": "https://wpnews.pro/news/my-coding-agent-kept-inventing-columns", "markdown": "https://wpnews.pro/news/my-coding-agent-kept-inventing-columns.md", "text": "https://wpnews.pro/news/my-coding-agent-kept-inventing-columns.txt", "jsonld": "https://wpnews.pro/news/my-coding-agent-kept-inventing-columns.jsonld"}}