Workivo
Menu

Workivo Docs: Agents

Agents

Build automated agents using the tools already in Workivo. No new code needed for most use cases — configure tables, jobs, and rules through the UI.

Anatomy of an agent

Every agent is just three things wired together:

  1. Config table — a custom table to store the agent's settings, state, and results
  2. Job — a recurring Ruby script that does the work (checks something, sends something, processes data)
  3. Logs — the agent writes to Log records so you can see what happened

Step by step

1. Create the config table

Go to Tables → Create table (e.g. weather_checks). Add whatever columns you need:

  • name (string) — label for this agent instance
  • city (string) — what to check
  • threshold (float) — alert threshold
  • last_result (text) — latest output
  • last_checked_at (datetime) — when it last ran

2. Define the form and list

After creating columns, go to the table's list page and click New Record. The system auto-generates a form from your columns. Tweak it by editing the form definition to hide internal fields, add help text, or change types.

3. Create the job

Go to Jobs → New Record. Set:

  • Name — describes what it does
  • Run type — Recurring (every N minutes)
  • Every (minutes) — how often it checks
  • Code — the Ruby script (see examples below)

4. Write the agent code

The job's code runs via bin/rails runner. It has full access to models, the database, and external APIs. Common patterns:

Check something and log it

WeatherCheck.find_each do |record|
  temp = fetch_temperature(record.city)
  Log.create!(
    message: "#{record.city}: #{temp} F",
    source: "agent.weather",
    context: { city: record.city, temp: temp }.to_json
  )
  record.update!(last_result: temp.to_s, last_checked_at: Time.current)
end

Send an alert when threshold is met

WeatherCheck.find_each do |record|
  temp = fetch_temperature(record.city)
  if temp > record.threshold
    Log.create!(
      message: "ALERT: #{record.city} at #{temp}F (threshold: #{record.threshold})",
      source: "agent.weather.alert"
    )
  end
  record.update!(last_result: temp.to_s, last_checked_at: Time.current)
end

Process data from another table

Log.where("created_at < ?", 7.days.ago).delete_all
Log.create!(message: "Cleaned logs older than 7 days", source: "agent.cleanup")

5. Monitor via logs

All agent output goes to Logs. Use the source field to filter — e.g. agent.weather shows only weather agent activity. Create a custom list definition for Logs filtered to a specific source if you want a dedicated view.

6. Wire up the sidebar

Go to Sidebar Items → New Record. Add a link to your agent's list page so it's one click away from the nav.

Going further

Rules for event-driven agents

If your agent needs to react to record changes (not just run on a schedule), use Rules. Create a rule file in app/services/rules/your_table/your_rule.rb. The rule fires on create/update/destroy of records. See Rules docs for the full pattern.

External APIs

Job code runs in a full Rails context. You can use Net::HTTP, Faraday, or any gem. Add it to the Gemfile, bundle install, restart, and use it in your job code.

Testing in the console

Use the Console to test agent code interactively before saving it as a job. Run your script line by line, inspect variables, and verify it works.

Forms with HTML widgets

For agent config tables, use the html field type in the form definition to show computed previews, status badges, or links. Template variables like {{record.last_result}} render live values on the record view.

Example: a weather monitor agent

Here's what a complete agent looks like using only the UI:

  1. Create table weather_checks with columns: name, city, threshold, last_result, last_checked_at
  2. Add a record: city=Chicago, threshold=90
  3. Create a recurring job (every 10 min) with code that fetches weather and logs results
  4. Check Logs to see it running

The entire agent is configurable from the UI — no deployment, no migrations beyond the initial table creation.

Back:

Please confirm

Are you sure?