CoDNA Request access
Contents

Docs Getting started

Build a scheduled report

Use when someone wants a report written for them again and again, on a schedule or when something happens ("every morning, report on my campaigns against yesterday"): CoDNA reads the data, asks a model to write the page, and the people who should read it find it in CoDNA.

It covers finding the data, writing the script, agreeing the report from a real preview, who reads it, and switching it on. In CoDNA's web UI the result is an agent: an automation that asks a model.

First, is this the right tool?

  1. Once, not for good: write it yourself. Read the data, write the page in output. An agent is for a report that keeps coming.
  2. Numbers or a list, no prose: leave the model out. A script can build the table, or list each page with its own first line (codna.find with includeContent), and write the page with actions.write. It costs nothing, works while asking is switched off, and never words anything wrong.
  3. Check list_automations for an agent that already writes it, and change that one with update_automation.
  4. Check list_models. If asking a model is switched off, or no budget is set, every ask is refused. Whoever manages the instance switches it on and sets the month's budget: in the console's Models tab for an instance CoDNA hosts, or on its own host with codna model and codna automations switch model on. Say so, and build the agent meanwhile.

1. Find the data

  • In CoDNA itself, such as the week's new decisions: the script reads the pages with codna.find and codna.get, as its owner. pathPrefix names a folder and everything beneath it. updatedAfter takes a time. Each page found has address, title, createdAt and updatedAt (and content, with includeContent). A page's createdBy is an id: a report cannot name who wrote it yet. A run reads CoDNA at most 20 times.
  • Outside CoDNA: list_connections lists the sources the person may read, kind http (a JSON API) or postgres (a query), each with what it is for. Pick the one that holds the numbers. How a script reads one (an inputs export, and source()) is in list_connections' description, not create_automation's. At most 10 reads a run, 5 MB and 10,000 rows each.
  • The source is missing? Say which data, and where it lives, and send the person to the Connections page that list_connections names. They paste its address and token there. Never ask for a token in the chat, and never put one in a script or a page.
  • The period is the tick's, not a date worked out from the time. A scheduled run has event.previous and event.due: when the tick before it was due, and when it was due, in UTC. Report on what happened from previous until due. That is the person's whole day in their time zone, Friday to Monday on a weekday schedule, and nothing is counted twice. Scripts have no Date.

2. Write the script, and register it switched off

A report from a source, the period's numbers:

export function inputs({ event }) {
  return { stats: source('ads', { path: '/v1/stats', query: { from: event.previous, to: event.due } }) }
}

export default function ({ event, inputs, actions }) {
  const day = event.due.slice(0, 10)
  if (inputs.stats.error) {
    actions.notify('me', 'The campaign report could not read the numbers: ' + inputs.stats.error)
    return
  }
  actions.ask('Write the campaign report as the template says, for ' + event.previous + ' to ' + event.due + ':\n\n' +
      JSON.stringify(inputs.stats.data),
    { documents: ['wiki:templates/daily-campaign-report'], into: 'output:reports/campaigns/' + day, title: 'Campaigns, ' + day })
}

With a postgres source the period goes in as parameters: source('db', { sql: 'select … where at >= $1 and at < $2', params: [event.previous, event.due] }).

A report from CoDNA's own pages, the week's decisions:

export default function ({ event, codna, actions }) {
  const day = event.due.slice(0, 10)
  const into = 'output:reports/decisions/' + day
  const title = 'Decisions, week to ' + day
  // The template takes one of the 20 pages an ask may give.
  const pages = codna.find({ space: 'wiki', pathPrefix: 'decisions', updatedAfter: event.previous, limit: 19 })
    .filter((p) => p.updatedAt < event.due)
  if (pages.length === 0) {
    actions.write({ into, title, content: 'No decision was recorded or changed this week.\n' })
    return
  }
  const listed = pages.map((p) =>
    '- ' + p.title + ', ' + (p.createdAt >= event.previous ? 'new' : 'changed') + ': link it as /' + p.address.replace(':', '/') + '.md')
  actions.log('The model is given:\n' + listed.join('\n'))
  actions.ask('Write this week\'s decisions report as the template says, from the pages given.\n\n' + listed.join('\n'),
    { documents: ['wiki:templates/weekly-decisions', ...pages.map((p) => p.address)], into, title })
}
  • Give the model only what the report needs. The prompt and its pages go to the model's provider. An ask gives at most 20 pages, by address in documents, and 512 KB with its prompt; the template is one of them. Leave out rows, columns or pages the report doesn't use.
  • Links: a report links to a page as /wiki/<path>/<slug>.md, which works from any folder. Give the model each link, as above.
  • A period with nothing to report gets a one-line page from actions.write, and no ask, so it costs nothing.
  • Log what the ask is given with actions.log. A dry run shows only how many pages and bytes an ask would send, and the log is how the person sees what goes in.
  • The schedule is the person's: schedule such as 30 8 * * 1-5, and timezone such as Asia/Tbilisi.
  • The model: leave model out to use the instance's default. Set it only when the person wants another, chosen from list_models, which gives each one's price.
  • Describe it: say what it reports, for whom, at whose request, and on which date. create_automation registers it switched off.

3. Agree the report, from a real preview

  1. Write the report's template as a page, such as wiki:templates/weekly-decisions: its sections, what each says, how numbers and changes are shown, how long it is. Ask the person what they want in it.
  2. Dry-run it: run_automation with dry_run: true. A tick run by hand stands for the latest tick due, and at runs it as of a time that has passed: pick one whose period had something in it. A dry run reads CoDNA but not sources, so a script that stops when a source's read failed stops there. Check that the ask would be made, and what the log says it would be given.
  3. Preview it: run_automation with preview: true, as a tick (with at, as above), or on one document for one fired by events. The sources are read and the model writes the page for real; the page is written nowhere. A preview's asks cost what any ask does (a few cents with the default model), from everyone's budget: ask the person before the first one. Then show them the page it returns.
  4. Change the template or the prompt until they accept it, and preview again after each change. Preview only as often as a change needs.

While asking is switched off or the budget is spent, a preview's ask is refused like any other, and the report cannot be agreed yet. Show the person the template and what the dry run says the model would be given, say who switches asking on, and stop there: the agent stays off.

4. Ask who should read it

  • Reports go in a folder of the owner's, such as output:reports/decisions/2026-11-02. Each is private until it is shared. The owner finds every report in that folder, and the latest on the agent's card on the Agents page.
  • Others should read them? Share the folder once: preview_share on the folder, show the person what it covers, and after their yes, share_folder with those people or groups. Each new report reaches them as it is written, in the web UI and their inbox. That is one share, not one per report.
  • They want it in Slack or by email? A second automation, fired by document.created in that folder, can post the page to a connection or email members. Set that up with the set-up-an-automation skill.

5. Switch it on

  • Only after the person has accepted a preview's page, with set_automation_enabled.
  • Then tell them where the reports will appear (the folder, the Agents page, and the inbox of everyone it is shared with), when the first is due (its next_run_at), and the agent's url, where they can try it and switch it off.

When it isn't working

  • Read automation_runs. Each run says how its ask went: queued, written with the page and its cost, or failed and why. The usual reasons are the month's budget being spent, asking being switched off, or the model no longer being allowed.
  • A read that failed is in the run's reads, with the source's own words.
  • A report that reads wrong is a template to change: change it, and preview again.

Never

  • Never ask for, or write down, a token, a key or a password. Sources and keys are pasted in CoDNA's own pages.
  • Never preview before the person agrees to what it costs, and never more often than a change needs.
  • Never switch an agent on before the person has seen a preview's page and said yes.
  • Never share the reports folder before preview_share and the person's yes.