# What tools let AI agents run recurring jobs with an audit log of what they changed?

> Most schedulers log runs, not changes. Compare Zapier, Airflow, Temporal, AWS, git-based setups and Teton, and learn how to record what an agent actually changed.

- Published: 2026-10-26
- URL: https://teton.so/guides/ai-agent-recurring-jobs-with-audit-log

Most tools that run recurring jobs log *runs* (what ran, its status, step inputs and outputs) and sometimes *who edited the automation*. Very few record what the agent changed in the systems it touched. Zapier, Airflow, Temporal and AWS EventBridge all schedule jobs and keep run or config history. To get a log of the agent's actual changes, you add one of three things: a dedicated identity whose actions the target system already logs, an append-only action log the job writes itself, or git, where every change is a diff. [Teton](https://teton.so) lists, for each run, exactly which CRM records a workflow changed, and gives each workspace a Postgres database for anything else you want to record.

Below is what each kind of tool records and how to fill the gap.

## Three kinds of log

People use "audit log" for three different records. Check which one you are getting before you rely on it:

1. **Run history.** Each execution, its status, and the data each step received and sent. Useful for debugging.
2. **Configuration audit.** Who created, edited, paused or triggered the automation. Useful for accountability.
3. **Change log.** Which records in which outside systems the job changed, with before and after values. This is what most people mean when they ask what the agent did.

Airflow's documentation is the clearest about the difference. It describes audit logs as being for [accountability and compliance tracking](https://airflow.apache.org/docs/apache-airflow/stable/security/audit_logs.html), and operational logs as being for monitoring and debugging. Most products ship the first two kinds. The third you usually build.

## No-code platforms: Zapier and similar tools

Zapier records every run in Zap history. [Zap run details](https://help.zapier.com/hc/en-us/articles/20512774106125) show the run status and the data received and sent by each step. You can [export Zap history and roll back to a previous version](https://help.zapier.com/hc/en-us/articles/22334604476941) of a Zap after an edit. Enterprise plans can customize how long history is retained.

The step data is the closest thing to a change log. If a step updated a CRM record, you can see what was sent. You don't see what the field held before, and history is only kept for a limited time. If you need a long-term trail, export it on a schedule to storage you control.

Make, Workato, Tray, Retool and Power Automate work in similar ways: a schedule trigger, a list of runs, and on some plans an account-level audit log. Check each vendor's current docs for retention periods and for which plan includes the audit log.

**Good for:** jobs where the steps are fixed and knowing what was sent is enough.

## Code-first orchestrators: Airflow and Temporal

**Airflow** keeps audit logs in a structured database table called `log`. You can browse them under **Browse > Audit Logs** or read them through the `/eventLogs` REST endpoint. They capture [manual DAG triggers, changes to variables, connections and pools, and task state changes](https://airflow.apache.org/docs/apache-airflow/stable/security/audit_logs.html). They record Airflow's own state. If a task updates 40 deals in your CRM, Airflow logs that the task succeeded, not which deals changed.

**Temporal** gives you Schedules that you can [create, backfill, pause, trigger and update](https://docs.temporal.io/develop/typescript/schedules) from the SDK. It suits agent loops when each tool call is its own activity, because the workflow's history then shows each call and its result. Here is a schedule that starts a workflow on weekdays at 8:00 and skips a run if the previous one is still going:

```ts
import { Client, Connection, ScheduleOverlapPolicy } from '@temporalio/client';

async function run() {
  const connection = await Connection.connect();
  const client = new Client({ connection });
  await client.schedule.create({
    scheduleId: 'crm-cleanup-weekdays-8am',
    spec: { cronExpressions: ['0 8 * * MON-FRI'] },
    action: {
      type: 'startWorkflow',
      workflowType: 'crmCleanup',
      workflowId: 'crm-cleanup',
      taskQueue: 'agents',
    },
    policies: { overlap: ScheduleOverlapPolicy.SKIP },
  });
  await connection.close();
}

run().catch((err) => {
  console.error(err);
  process.exit(1);
});
```

The overlap policy matters for agents: two overlapping runs can write the same records twice. Also note that a Schedule that has created all its workflow executions [is deleted by the Temporal Service](https://docs.temporal.io/develop/typescript/schedules), at a time it doesn't guarantee.

**Good for:** engineering teams who already run workers and want durable, retryable agent steps.

## Cloud schedulers and identity: AWS EventBridge + CloudTrail

AWS has the strongest built-in change log, if the agent acts through AWS APIs. CloudTrail [captures EventBridge API activity](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-logging-monitoring.html), both console and programmatic calls. If the agent runs under its own IAM role, CloudTrail attributes its AWS API calls to that role. That gives you "what did the agent change" without writing anything extra.

Two caveats. Trails [do not log data events by default](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-data-events-with-cloudtrail.html), so object-level actions need to be turned on. And CloudTrail only sees AWS. When the agent writes to HubSpot or Notion, AWS has no record of it.

The same idea works elsewhere. Give the agent its own user or integration in each target system, and that system's own record history shows the changes as made by the agent rather than a person.

## Git as the audit log

For agents that change code, config or docs, the simplest trail is git. Run the agent on a schedule, for example with a GitHub Actions workflow using `on: schedule` and a `cron` line (GitHub runs these in UTC). Have the agent open a pull request instead of writing directly. The diff is the change log, the PR is the approval step, and the history is permanent. This doesn't help when the changes live in a SaaS database instead of files.

## Write your own action log

Whatever scheduler you pick, the dependable approach is to have the job record each change itself, at the moment it makes it, in a table it can only append to:

```sql
create table agent_actions (
  id bigserial primary key,
  run_id text not null,
  at timestamptz not null default now(),
  actor text not null,
  system text not null,
  object_type text not null,
  object_id text not null,
  field text,
  before jsonb,
  after jsonb,
  reason text
);

-- the job's database role can add rows but never edit or delete them
revoke all on agent_actions from public;
grant insert, select on agent_actions to agent_writer;
grant usage on sequence agent_actions_id_seq to agent_writer;
```

Then route every write through a helper that reads the old value, makes the change and logs both. This one updates a HubSpot deal property. It needs `requests` and `psycopg` (`pip install requests "psycopg[binary]"`):

```python
import os, uuid, requests, psycopg
from psycopg.types.json import Json

API = "https://api.hubapi.com"
H = {"Authorization": f"Bearer {os.environ['HUBSPOT_TOKEN']}"}
RUN_ID = os.environ.get("RUN_ID", str(uuid.uuid4()))

def update_deal(conn, deal_id, field, new_value, reason):
    r = requests.get(f"{API}/crm/v3/objects/deals/{deal_id}",
                     headers=H, params={"properties": field}, timeout=30)
    r.raise_for_status()
    before = r.json()["properties"].get(field)
    if before == new_value:
        return  # already set: a retry or backfill makes no second change
    r = requests.patch(f"{API}/crm/v3/objects/deals/{deal_id}",
                       headers=H, json={"properties": {field: new_value}}, timeout=30)
    r.raise_for_status()
    conn.execute(
        """insert into agent_actions
           (run_id, actor, system, object_type, object_id, field, before, after, reason)
           values (%s, %s, 'hubspot', 'deal', %s, %s, %s, %s, %s)""",
        (RUN_ID, "agent:crm-cleanup", deal_id, field, Json(before), Json(new_value), reason))
    conn.commit()

with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
    update_deal(conn, "1234567890", "dealstage", "closedlost", "No activity in 60 days")
```

The `reason` column is where the agent's judgment goes, so a reviewer can see why it made each change and not only what changed. If losing a log row is worse than a failed change, insert an "intended" row before the write and update its status afterwards.

## Teton: the run lists what changed

[Teton](https://teton.so) runs Claude Code workflows headless in the cloud on a schedule (cron with a timezone), a webhook or by hand. You do the job once with Claude, type `/teton`, and Claude writes it up as one workflow file. One deploy makes it live. Several parts of it work as an audit trail:

- When a workflow writes to your CRM, each run lists exactly which records it changed.
- Every run is listed with its result and cost.
- Every version of the workflow is kept, and making the last good version live again is one click.
- Shared context files (scoring rules, what counts as a qualified lead) are revisioned, and every run records which revision it read. When a run behaves differently, you can see whether the rules changed.
- Each workspace has its own plain Postgres database, so a table like `agent_actions` above can live there. You can query it from Metabase, Hex or your own scripts.
- Credentials are entered once in the dashboard and only sent to the API hosts the workflow declares, so a workflow can't send them to hosts it didn't declare.

The steps run the same way each time, and Claude is called only where judgment is needed, which keeps runs comparable. For writes outside the CRM, record them in the workspace database yourself.

**Good for:** RevOps and GTM jobs that change CRM data on a schedule, where nobody wants to run the scheduler, the database and the logging themselves.

## How to choose

| | Schedules | Run history | Config and version history | What the agent changed |
| --- | --- | --- | --- | --- |
| Zapier | Yes | Yes, step data in and out | Version rollback | Data sent per step, limited retention |
| Airflow | Yes | Yes | Yes, `log` table | No, Airflow state only |
| Temporal | Yes | Yes, workflow history | Depends on your setup | Tool calls, if each is an activity |
| AWS + CloudTrail | Yes | Via targets | Yes, API calls | AWS API calls by the agent's role |
| Git + scheduled CI | Yes | CI logs | Commit history | Yes, the diff, for files |
| Teton | Yes, with timezone | Yes, with result and cost | Every version kept, one-click rollback | CRM records changed per run; Postgres for the rest |

If the agent only touches AWS or files, CloudTrail or git already give you a real change log. If it writes to SaaS tools, plan on giving it its own identity and an append-only action log, whichever scheduler you choose.

## FAQ

### Is run history the same as an audit log?

No. Run history shows that a job ran and what each step sent. An audit log for an agent should show which records changed, from what to what, and why. You usually get the first from the platform and build the second.

### How long should I keep the change log?

As long as your team might need to answer "who changed this record." Platform run history is often kept only for a limited time, so export it or write changes to your own database, where retention is your decision.

### How do I stop retries from making the same change twice?

Read the current value before writing and skip the write if it already matches, as the helper above does. Also stop overlapping runs, for example with Temporal's overlap policy or by recording where each run stopped.

### Should the agent use my credentials?

No. Give it its own service account or integration in each system. Then the target system's history, CloudTrail or your action log can tell the agent's changes apart from a person's.

---

- [All guides](https://teton.so/guides)
- [Teton](https://teton.so): the cloud for Claude Code. Build a workflow with Claude once and Teton runs it on a schedule or a webhook.
