{"slug": "agentic-reporting-without-data-exposure", "title": "Agentic reporting without data exposure", "summary": "A developer demonstrated a reporting workflow in Bino, an open-source BI tool, that lets AI agents design reports without ever seeing real company figures. The approach keeps a synthetic dataset with matching columns, types and special cases for the agent to work against, while a data-source constraint and a REPORT_DATA environment variable default to synthetic and only switch to real data in CI, where the API token lives. The build embeds the active data label in the PDF footer so each report states which data it was built from.", "body_md": "AI agents are good at building reports. Give one a data file and a goal, and a few minutes later you have a table, a chart, and a PDF. Most tools make that possible in the simplest way: the agent gets the data. Revenue, margins, costs, customer lists go into the prompt, and every value ends up with the model provider.\n\nFor source code, many companies have made their peace with that. For company figures, most have not, and for good reasons: contracts, data protection, insider rules, or simply the feeling that next quarter's numbers should not leave the house before the board has seen them.\n\nThis post shows a way out that doesn't require a special model or a new contract. The agent writes the report. It never sees the real numbers. We built a small demo report, ran it with the Bino CLI and with the agent tools in `bino mcp`, and you can download it and run it yourself.\n\nThe idea rests on a simple observation: to design a report, you need to know the **structure** of the data, not the **values**. Which columns exist, what type they are, what range they fall in, which special cases occur. Whether North sold 11.5 or 17.2 million in September does not change where the table goes or how the chart is scaled.\n\nIn most reporting tools, the design and data are mixed from the start. The numbers are on a slide. A dashboard is bound to a live connection. An agent that builds one has to touch the real data, because there is nothing else to touch.\n\nIn Bino, a report is plain text: YAML for pages, tables and charts, SQL for the data. The data and the design meet in exactly one place, the build. Before that, the report is only a blueprint, so you can hand the blueprint to an agent and keep the data for yourself.\n\n**1. A synthetic dataset.** Next to the report, you keep a small file with made-up values. The columns are the same, the types are the same, the magnitudes are similar, and the special cases are the same as in the real data. The agent works with this file.\n\n**2. Two sources with the same name.** The report reads a source called `sales`. There are two definitions of it. A constraint on each one decides which one is active:\n\n```\n# Synthetic: the default. This is what you and your agent work with.\napiVersion: bino.bi/v1alpha1\nkind: DataSource\nmetadata:\n  name: sales\n  constraints: [labels.data==synthetic]\nspec:\n  type: csv\n  path: ../data/synthetic/sales.csv\n---\n# Real: only used when REPORT_DATA=real. Only CI has the token.\napiVersion: bino.bi/v1alpha1\nkind: DataSource\nmetadata:\n  name: sales\n  constraints: [labels.data==real]\nspec:\n  type: csv\n  path: ${SALES_URL:https://data.example.com/sales.csv}\n---\napiVersion: bino.bi/v1alpha1\nkind: ConnectionSecret\nmetadata:\n  name: salesApi\nspec:\n  type: http\n  http:\n    bearerTokenFromEnv: SALES_API_TOKEN\n```\n\nThe SQL and the layout only ever say `sales`. They do not know which source they get, and they do not need to.\n\n**3. A switch that only CI sets.** The report itself carries a label, and the label comes from an environment variable with a safe default:\n\n```\napiVersion: bino.bi/v1alpha1\nkind: ReportArtefact\nmetadata:\n  name: monthly\n  labels:\n    data: ${REPORT_DATA:synthetic}\n```\n\nWithout the variable, the label is `synthetic` and Bino picks the synthetic source. That is true for `bino build` and `bino preview` on your laptop, and for every build the agent starts through `bino mcp`. Only the CI job sets `REPORT_DATA=real`, and only the CI job has the token for the data API.\n\nWe also put the label into the page footer (`footerText: \"Data: ${REPORT_DATA:synthetic}\"`), so every PDF says which data it was built from. It's a small thing, but it ends every \"wait, which numbers is this?\" discussion.\n\nHere is the demo report, which was built twice from the same files. The first one is what the agent sees while it works. The second one is what CI produces.\n\nThe layout, scaling, and notation are all the same. The figures change, and the footer tells you which ones you're looking at.\n\nThe agent works with Bino through `bino mcp`, an MCP server that gives it tools to read the schema, inspect data, write manifests, build and check the result. We drove that server the way an agent would, with no `REPORT_DATA` set, and asked for rows from `sales`:\n\n```\n{\n  \"name\": \"$sales\",\n  \"columns\": [\"date\", \"region\", \"region_index\", \"actual\", \"plan\"],\n  \"rows\": [\n    { \"date\": \"2026-01-01T00:00:00Z\", \"region\": \"North\", \"region_index\": 1,\n      \"actual\": 1037208, \"plan\": 1200000 }\n  ]\n}\n```\n\nThese are the synthetic rows. The `build` tool produced the synthetic PDF, and our stand-in data API logged no request from the agent at all. The agent can still do its job: it sees the columns, sees realistic values, builds the PDF and checks the layout with `get_layout_state`. It just never sees a real number.\n\nTo help the agent behave, the demo includes an `AGENTS.md` (and a copy as `CLAUDE.md`) with four rules: work against `data/synthetic/`, never set `REPORT_DATA`, `SALES_URL` or `SALES_API_TOKEN`, never try to reach the real source, and add missing edge cases to the synthetic file instead.\n\nThe real report is built by a standard CI job. There is no model, agent, or prompt. It checks out the repository, installs Bino, and builds with the real data:\n\n```\njobs:\n  build:\n    runs-on: ubuntu-latest\n    env:\n      CI: \"1\"\n      REPORT_DATA: real\n      SALES_URL: ${{ vars.SALES_URL }}\n      SALES_API_TOKEN: ${{ secrets.SALES_API_TOKEN }}\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install bino\n        run: |\n          /bin/bash -c \"$(curl -fsSL https://github.com/bino-bi/bino-cli-releases/releases/latest/download/install.sh)\"\n          bino setup\n      - name: Build with real data\n        run: bino build --data-validation=fail --layout-state\n```\n\n`--data-validation=fail` stops the build if the real data does not fit what the report expects. `--layout-state` reports empty charts, overflow and shrunk fonts as warnings, so you see when the real numbers break a layout the synthetic ones did not. We tested both directions: with the right token, the build reads the real data and produces the real PDF. With a wrong token, the data API answers 401 and the build fails. No token, no report.\n\nWhile we were building the demo, we learned one thing above all: good synthetic data is hard to make. A file with the right columns is the easy part. The hard part is everything that makes the layout break later: the magnitudes, the seasons, a plan that moves with the actuals, the one region that is ten times bigger than the rest, months with no sales, names that are too long for a column.\n\nIf you write the file by hand, it starts to drift the day you finish it. The real data gets a new region or a new product, and nobody updates the synthetic file. If you create it from the real data, you have a new risk: a generator that copies real values, keeps a rare outlier, or shows a group of three customers can leak exactly what you wanted to protect.\n\nWe want to solve this problem next, in [Sluice](https://bino-bi.github.io/sluice/). Sluice sits between AI agents and your databases and checks every query against your rules: row filters, column masks, approvals, and an audit log that cannot be changed without being noticed. It speaks SQL over REST and MCP.\n\nWe are now adding synthetic data sources to Sluice. Sluice reads the real source once, builds a profile of every column, and writes generated rows that keep the distributions, the relations between tables, and the correlations between columns. Small groups are dropped, extremes are moved, and noise can be added, so the result does not show single records. The agent then queries the synthetic copy like any other database. This is still in development and not in a release yet.\n\nIf you want to see where this goes, [follow us on GitHub](https://github.com/bino-bi). Or [send us a message](https://dev.to/contact): we are looking for teams who want to try it with their own data, and we would like to hear how you keep figures away from AI models today.\n\nThe demo is a complete Bino project: synthetic data, the two sources, the report, the CI workflow and the agent rules.\n\n[**Download the demo project**](https://www.bino.bi/downloads/data-separation-demo.zip) (ZIP, 6 KB)\n\nUnpack it and run:\n\n```\nbino build      # dist/monthly-sales.pdf, the footer says \"Data: synthetic\"\nbino preview    # live preview while you or your agent work on the layout\n```\n\nTo connect an agent, add the MCP server (or install the Bino plugin for Claude Code) and ask it to change the report:\n\n```\nclaude mcp add bino -- bino mcp --work-dir \"$PWD\"\n```\n\nTo see the real build, put a CSV with the same columns behind any URL that takes a bearer token, and set `REPORT_DATA=real`, `SALES_URL` and `SALES_API_TOKEN`. The README in the project has the details.\n\nIf you need to explain this to your data protection officer, your IT department, or your CFO, we put the idea on one sheet of paper. On the front side, we explain the problem and five rules. On the back side, we explain how it works in Bino.\n\nThe short version: let the agent write the blueprint with data that means nothing, and let a build without AI add the numbers that mean everything.", "url": "https://wpnews.pro/news/agentic-reporting-without-data-exposure", "canonical_source": "https://dev.to/thatscalaguy/agentic-reporting-without-data-exposure-5fcb", "published_at": "2026-11-04 13:16:00+00:00", "updated_at": "2026-11-04 13:28:31.359133+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-safety"], "entities": ["Bino", "Bino CLI", "bino mcp"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/agentic-reporting-without-data-exposure", "markdown": "https://wpnews.pro/news/agentic-reporting-without-data-exposure.md", "text": "https://wpnews.pro/news/agentic-reporting-without-data-exposure.txt", "jsonld": "https://wpnews.pro/news/agentic-reporting-without-data-exposure.jsonld"}}