agent.run(files=[File(...)]) handles one document. Production runs see folders, queues, and nightly drops. Three primitives cover the production shape: concurrent runs for ad-hoc batches, background runs for long jobs, scheduled runs for nightly intake.
Concurrent batch over a list
The simplest batch is a folder of files.agent.arun is async, so a semaphore plus asyncio.gather is enough.
concurrency to the slowest of: your rate quota, your DB write throughput, your memory budget.
Background runs for long jobs
Sync runs hold an HTTP connection until the model returns. For multi-page contracts or scanned PDFs that take minutes, start the run in the background and poll.db. The agent process can restart and a different process can poll the same run_id. That is the durability property: state lives in the database, not in the calling process.
Scheduled batch with retries
For nightly intake (an SFTP drop, a Drive folder, a queue), put an AgentOS in front of your agent and let the scheduler fire the run on cron.ScheduleManager writes to the same db the AgentOS polls.
if_exists="update" makes the call idempotent. Re-running the bootstrap script does not create duplicates. The scheduler retries on HTTP failure with the configured delay, and every fire writes a row to agno_schedule_runs with status and timing.
Pattern comparison
The scheduler fires endpoints. Endpoints are agents, teams, or workflows. So a nightly job that ingests a folder, extracts each file, and writes to your warehouse is a workflow exposed at
/workflows/<id>/runs, scheduled with the same ScheduleManager.create call. See Workflows.
Observability
Every scheduled fire creates a row inagno_schedule_runs with the schedule id, attempt number, status, and the run_id of the underlying agent run. To see the last day of activity:
schedule_id and an incrementing attempt. That is the audit trail you can hand to ops.