|
| 1 | +--- |
| 2 | +title: "The Constraint Isn't Typing Speed. It's the Context Window." |
| 3 | +description: AI can write an enterprise app. Whether it can maintain one depends on whether the whole system fits in its head — which is an argument about the target format, not the model. |
| 4 | +author: ObjectStack Team |
| 5 | +date: 2026-07-17 |
| 6 | +tags: [ai, architecture, metadata, positioning] |
| 7 | +--- |
| 8 | + |
| 9 | +Point a coding agent at an empty repository and ask for an expense-approval |
| 10 | +system. You'll get one. It'll have a schema, some endpoints, a couple of React |
| 11 | +screens, a permissions check or two. It'll take an afternoon instead of a |
| 12 | +quarter, and it'll mostly work. |
| 13 | + |
| 14 | +Now come back in six weeks and ask for a change: expenses over $50k need a |
| 15 | +second approver from Finance. |
| 16 | + |
| 17 | +This is where the afternoon's magic runs out. The agent has to find every place |
| 18 | +that assumed one approver — the schema, the approval logic, the UI that renders |
| 19 | +the button, the permission check, the notification text, the test fixtures. |
| 20 | +Twelve files, maybe thirty. It reads what it can, misses what it can't, and |
| 21 | +ships something that type-checks and quietly does the wrong thing on the 5% path |
| 22 | +nobody clicks until quarter-end. |
| 23 | + |
| 24 | +The problem wasn't the model's intelligence. **The problem was that the system |
| 25 | +no longer fit in its head.** |
| 26 | + |
| 27 | +## The number that actually matters |
| 28 | + |
| 29 | +There's a temptation to sell AI-native development on volume: *N× less code, M× |
| 30 | +faster*. Those numbers are marketing until someone measures them, and the honest |
| 31 | +truth is that "how many lines would the traditional version have been?" is a |
| 32 | +counterfactual nobody can measure — because that version doesn't exist. |
| 33 | + |
| 34 | +Here's what *can* be measured. This is a real, working CRM in this repository — |
| 35 | +[`examples/app-crm`](https://github.com/objectstack-ai/framework/tree/main/examples/app-crm): |
| 36 | +accounts, contacts, leads, opportunities with line items, activities. Views, |
| 37 | +a dashboard, a lead-conversion flow, permission sets, an action, translations. |
| 38 | + |
| 39 | +| | | |
| 40 | +| :--- | :--- | |
| 41 | +| Files | 31 | |
| 42 | +| Lines | 1,792 | |
| 43 | +| Approximate tokens | ~16,000 | |
| 44 | + |
| 45 | +That's the **entire business system**. Not a module of it — the data model, the |
| 46 | +UI, the automation, the access rules, all of it. It fits in about 8% of a |
| 47 | +200k-token context window. |
| 48 | + |
| 49 | +The Todo example is ~14.5k tokens. The kitchen-sink showcase app — which |
| 50 | +deliberately exercises every metadata type in the protocol, 83 files of it — is |
| 51 | +~101k. Even that one, the maximalist case, leaves half a modern context window |
| 52 | +free for the actual work. |
| 53 | + |
| 54 | +Measure your own: |
| 55 | + |
| 56 | +```bash |
| 57 | +find src -name '*.ts' -not -name '*.test.ts' | xargs cat | wc -l |
| 58 | +``` |
| 59 | + |
| 60 | +## Why "fits in the window" is a different claim |
| 61 | + |
| 62 | +"Less code" is an efficiency argument. **"Fits in the window" is a capability |
| 63 | +argument** — it changes what the agent is able to do at all, not how fast it |
| 64 | +does it. |
| 65 | + |
| 66 | +An agent that can hold the whole system at once can answer questions that an |
| 67 | +agent working through a 40k-file repository fundamentally cannot: |
| 68 | + |
| 69 | +- *Which things break if I add a second approver?* — It can see every consumer |
| 70 | + of the approval, because every consumer is in the same few thousand lines. |
| 71 | +- *Is this change safe?* — It can check the permission set against the view |
| 72 | + against the flow, in one pass, without hoping its grep found everything. |
| 73 | +- *What did I miss?* — There's no "somewhere else in the codebase" for a |
| 74 | + dependency to hide in. |
| 75 | + |
| 76 | +This is the line between an agent that autocompletes and an agent that |
| 77 | +maintains. Retrieval helps a model *find* things; it doesn't give it the whole |
| 78 | +dependency graph at once. And for the change above, the whole graph is exactly |
| 79 | +what you need. |
| 80 | + |
| 81 | +## How the system gets small |
| 82 | + |
| 83 | +It isn't compression, and it isn't a clever DSL. It's that most of an enterprise |
| 84 | +app was never business-specific to begin with. |
| 85 | + |
| 86 | +Pagination. Sort order. Optimistic concurrency. Foreign-key resolution. Form |
| 87 | +validation mirrored on the client and the server. Audit rows. Permission checks |
| 88 | +on every endpoint. The list view that needs a filter bar. **None of that is your |
| 89 | +business** — it's the same in every CRM ever written, and traditionally you (or |
| 90 | +your agent) rewrite it per app, per screen, because there's nowhere else to put |
| 91 | +it. |
| 92 | + |
| 93 | +Put it in the runtime instead, and what's left in the source is the part that's |
| 94 | +actually yours: |
| 95 | + |
| 96 | +```ts |
| 97 | +export const Opportunity = ObjectSchema.create({ |
| 98 | + name: 'crm_opportunity', |
| 99 | + label: 'Opportunity', |
| 100 | + sharingModel: 'private', |
| 101 | + fields: { |
| 102 | + name: Field.text({ label: 'Opportunity Name', required: true }), |
| 103 | + account: Field.lookup('crm_account', { label: 'Account', required: true }), |
| 104 | + amount: Field.currency({ label: 'Amount', min: 0 }), |
| 105 | + probability: Field.percent({ label: 'Probability', defaultValue: 0.5 }), |
| 106 | + expected_revenue: Field.formula({ |
| 107 | + label: 'Expected Revenue', |
| 108 | + expression: cel`amount * probability`, |
| 109 | + }), |
| 110 | + }, |
| 111 | +}); |
| 112 | +``` |
| 113 | + |
| 114 | +From that, the runtime derives the table, the REST endpoints, the list and form |
| 115 | +UI, and the MCP tools an agent can call. There is no generated code sitting in |
| 116 | +your repo that has to be regenerated, reviewed, and kept in sync — the surface |
| 117 | +*is* the definition, and the definition is what you read. |
| 118 | + |
| 119 | +This is what ObjectStack is for. Not "a framework that writes less code" — |
| 120 | +**a target format small enough that the thing writing it can also understand |
| 121 | +it.** |
| 122 | + |
| 123 | +## The catch, stated plainly |
| 124 | + |
| 125 | +A small system is only useful if it's a *correct* small system, and compactness |
| 126 | +cuts both ways: a one-line mistake in a few hundred lines of metadata is |
| 127 | +proportionally a much bigger mistake. |
| 128 | + |
| 129 | +Worse, the errors that matter here mostly don't crash. A predicate that |
| 130 | +references a field the wrong way doesn't throw — it evaluates to nothing, the |
| 131 | +button silently never renders, and you find out when a user asks why they can't |
| 132 | +resolve a ticket. TypeScript won't catch it: the predicate is a string, and the |
| 133 | +string is a perfectly valid string. |
| 134 | + |
| 135 | +That's why the gate exists. `os validate` runs the checks a type system |
| 136 | +structurally cannot: CEL predicates actually parse and reference fields that |
| 137 | +exist, widget bindings point at datasets that exist, every custom object |
| 138 | +declares its access baseline. It rejects at authoring time, with a located |
| 139 | +error the agent can read and fix in the same breath — which is the other half of |
| 140 | +why the host language is TypeScript. Not because it's fashionable, but because |
| 141 | +an agent's mistakes come back as *text it can act on*, seconds after it made |
| 142 | +them, instead of as a silent runtime failure six weeks later. |
| 143 | + |
| 144 | +Small enough to fit. Strict enough to be worth fitting. That's the whole bet. |
| 145 | + |
| 146 | +--- |
| 147 | + |
| 148 | +**Try the measurement yourself:** `npm create objectstack@latest my-app`, build |
| 149 | +something with your agent, then count the lines. If the whole app doesn't fit in |
| 150 | +your agent's context window, we'd like to hear about it — that's a bug in the |
| 151 | +target format, and the format is [open](https://github.com/objectstack-ai/framework). |
0 commit comments