10 MIN READ
FRANÇOIS CHALIFOUR

Interfaces for the next question
Our UI schema couldn't keep up with new questions, so we built agent-written Data Apps with flexible interfaces and stable data contracts.
"Why did our infrastructure bill jump last month?"
Our first answer at Altertable was a dashboard. The agent generated JSON, and a renderer turned it into metrics, charts, and tables. A chart showed spending by service, and storage explained most of the increase.
Then came the next question: which customers were driving it? What would happen if their usage doubled?
Each question called for a different interaction: a customer table linked to the chart, a growth assumption you can edit, a forecast that updates as you explore. Every interaction outside our predefined components would have required extending the UI schema, its renderer, and the agent's instructions. A UI schema is a prediction about the interactions people will need, and ours was already wrong by the second question.
We stopped before going down that path and let agents build the application itself. They could use React and CSS, with our SDK handling data access.
We call these Data Apps: custom applications for exploration, analysis, and presentation, shaped by the questions people ask. From the start, we wanted them to run wherever people work: in Altertable, in agent hosts like Claude, ChatGPT, and Cursor, and embedded in other products.
We then had to decide what the agent could change and what the platform had to enforce. The design had to support interfaces that change with each question while keeping metric definitions, evidence, and data access consistent.
The interface follows the question
The agent builds an app showing storage spending by customer. You can filter the customer table, choose a month, or adjust a growth assumption yourself. Choosing a month fetches that month's data; moving the usage slider recalculates the forecast from the data already loaded. If you then want a regional comparison, ask the agent to add it to the app.
Conversation builds the app. Controls make it worth coming back to.
A Data App is plain source code, so its behavior can evolve beyond a predefined set of widgets, with types and tests to check each change. You can duplicate it for another team, point it at another cloud account, brand it, review it, and version it. When we release a new SDK version, an agent migrates the app: it bumps the dependency, adapts the calls, and validates the result.
Build a normal SDK, then teach the domain
Fetching data, loading states, charts, and error handling are repeated work. That belongs in an SDK, and we built the open-source Data App SDK the way we'd build one for human developers: composable primitives, clear authoring APIs, TypeScript types, JSDoc, Markdown docs, and working examples. The same SDK works in local projects and in apps hosted by Altertable. A good SDK for people turns out to be a good SDK for agents.
The SDK explains how to build the app. Skills supply the domain knowledge needed to make it useful. A customer cost report, for example, may need to exclude internal test accounts. The agent needs to know which accounts those are and how to identify them in the data. Skills point it to the relevant business definitions and sources, just as a human developer would need that context before writing the query.
Keeping that knowledge outside the UI code means it survives changes to the interface. Adding a regional comparison shouldn't change which accounts the report excludes.
Authoring skills also give the agent a workflow outline, links to the docs each step needs, and a working starter matched to the SDK version. That saves repeated rounds of searching the docs and trying API calls before it can build the app.
Declare the data contract in the code
Skills guide the agent's choices. The manifest makes its data requirements explicit to the platform. Before an app runs, the platform needs to know what it is and which queries it will make. We didn't want a separate config file that drifts from the source, so the app declares its manifest with defineDataApp():
const dataApp = defineDataApp({title: 'Infrastructure costs',description: 'Find which services and customers drive the cloud bill.',scope: { organization: 'acme', environment: 'production' },appearance: { theme: 'system', density: 'compact' },queries: {costsByService: {statement: `SELECT service, sum(cost) AS total_costFROM infrastructure_costsWHERE month = $monthGROUP BY serviceORDER BY total_cost DESC`,params: {month: { type: 'string', defaultValue: '2026-09' },},projections: {service: { type: 'string' },total_cost: { type: 'number', nullable: true },},},},});
At build time, we parse the source and extract the literal argument to defineDataApp(). The platform reads the manifest without executing the app.
The declaration only holds static values, and that's deliberate. An app can't assemble SQL from strings at runtime. Everything that varies, like the selected month, reaches the query as a bound $parameter. That rules out injection by construction, and it makes caching safe: the platform knows every query an app can run before it runs any of them, so results can be keyed by query and parameters.
From that one declaration, we sync the app's title, description, and queries to the platform, which then controls how each query executes.
Keeping layout, state, and interactions in React lets the agent add a new interaction without changing the platform. The manifest stays limited to declared queries and the metadata a host needs before the app runs.
Every number carries its evidence
The query contract is also what connects a number on screen to the work that produced it.
The agent inspects the source tables and fields before writing SQL, then declares the query's parameters and expected result columns. When you select September in the app, it passes that month to the declared query as $month and uses the returned rows to update the chart.
Evidence links the displayed result to its query, parameters, and the business definitions used to produce it. If a customer cost report excludes internal test accounts, you can inspect that rule alongside the SQL and selected month. That makes it possible to check why its total differs from a report that includes every account. A number people can't trace is a number they won't trust.
A chart and the sentence explaining it also read from the same result. Change the month, and both update together, so the text can't keep describing the previous selection.
Close the loop
Contracts are only as useful as the agent's ability to check its work against them. In Altertable's hosted authoring workflow, a harness connects the agent to its tools and feeds the results back for the next edit. The agent works in a sandbox with a filesystem, a shell, and an authenticated Altertable CLI, where it reads SDK docs, edits source, runs queries, and validates. Developers using the SDK locally can work in their own editor and shell. The following stages show how our hosted workflow makes errors visible to the agent.
Feedback becomes the next edit.
Types first. An app hosted on Altertable is a single TypeScript React file with supported package imports. Validation sends it to our build service, which bundles it with esbuild, extracts the manifest, and returns TypeScript diagnostics with source locations. Wrong component props and invalid API calls fail here instead of at runtime, and the agent can fix and revalidate without saving a new revision. Types are the cheapest reviewer an agent has.
Then the browser. Types can't tell you whether a filter works, whether the results make sense, or whether the layout reads well. So the agent gets eyes: a screenshot tool renders the app in headless Chromium and returns the image to the agent. The authoring guide tells it what to look for: findings that match the source data, the loading, empty, and error states, and a layout that holds at narrow and wide widths in both themes.
Then the person. People need a precise way to ask for changes. You can select part of an app and annotate it: "Show only enterprise customers" on the chart, or "Exclude internal test accounts" on the customer table. In Altertable, annotations are built into the app viewer. In agent hosts, the host's own annotation system plays the same role. The agent receives each comment with a screenshot of the selected area, the element's linked queries and definitions, the current filters, and the source version. Several annotations can go in one request. The agent never has to guess which chart "this" refers to.
Then the runtime. Some failures only show up with real data, like a column renamed upstream after the app shipped. While it runs, the app captures failures in data fetching and rendering. The report is explicit about its limits: views nobody visited stay unverified. When something fails, our repair integration can start a repair conversation automatically for people allowed to manage the app. The agent gets the failing revision and one complete error report, edits the source, and the host loads the new revision. Healthy parts of the app stay visible while the repair runs. Automatic repair is capped, so an agent that can't fix a bug hands it to a person instead of looping.
Write once, host anywhere
Separating the interface from data access also lets the same app run across different hosts. Any product or internal portal can embed a Data App by implementing our host protocol and providing authorized data access.
For agent hosts, we use MCP Apps, the official MCP extension for interactive interfaces. Our MCP App viewer implements the same host protocol and forwards data requests through MCP tools.
The bundle runs in a sandboxed iframe. A message bridge connects its SDK client to the host and handles requests, cancellation, and connection state. Credentials and access limits never leave the backend. Switching hosts changes how the app reaches data, never the UI itself.
One app. Any host.
Switch hosts. Keep the same app and data.
Sends the registered query and parameters to the backend.
The host forwards the requested query and its parameters to the backend, which applies the viewer's permissions and runs the query through a read-only connection. Cached results are keyed by the query, its parameters, and the access context.
Hosted apps are a single file on purpose. One file meant we didn't need a coding agent or a git server in the platform to build and store an app. The cost is that larger apps get long, and server-only code has to go. Local apps use the same SDK with a local server handling data requests over HTTP. An agent can convert one into the other by swapping the transport and checking that both reach the same data.
So the storage investigation can start as a question in Claude, become an app the finance team opens in Altertable, and move into the team's codebase when it becomes a tool they use every week. The app moves between tools and grows with the investigation, while its data definitions remain consistent.
Contracts hold, interfaces follow
The platform holds what must stay true: the data contracts, access control, and feedback loops. The app's code handles everything people will ask next.
A dashboard answers the questions you planned for. A Data App lets you ask the next one.
François Chalifour
Staff Frontend Engineer
Passionate about developer experience and building tools that empower teams. Expert in modern web technologies and API design.




