Skip to main content

Tech Stack & Design Principles

How the MR People team agents are built, deployed, and operated.

Platform: Google Apps Script

All agents run on Google Apps Script, which is JavaScript that executes inside Google's infrastructure. It's not Node.js or browser JavaScript. It has direct access to Google Workspace services (Sheets, Drive, Gmail) without any setup or authentication.

Why Apps Script?

  • The People team already lives in Google Workspace (Sheets, Drive, Gmail). Apps Script plugs into all of it natively.
  • No servers to manage. Google runs the code.
  • The team can see and edit the spreadsheets that configure the agents without touching code.
  • Deployable under the cmprssn@madison-reed.com service account (no IT gate needed).

Limitations to know

  • No npm packages. The ecosystem is closed. Every utility (CSV parsing, JSON validation, rule engine, etc.) is hand-rolled in the shared/ library.
  • Flat global scope. All files in a project share one namespace. No modules, no imports.
  • 6-minute execution limit per run (Google's hard limit for Apps Script).
  • No SFTP support. Apps Script can't push files over SFTP directly, so that step stays human or uses a GitHub Action as a courier.

Code structure

product/mr-people-agents/
├── shared/ ← Reusable libraries (CSV, rules engine, wage logic, audit logging, etc.)
├── recon/ ← Pay reconciliation agent
│ ├── apps-script/ ← The actual Apps Script project files
│ └── gs-paste/ ← Same files formatted for manual paste into the Script editor
├── minwage/ ← Minimum wage monitor agent
│ └── apps-script/
├── leave/ ← Leave agent
│ └── apps-script/
├── tests/ ← Node.js test suite (56 tests, zero dependencies)
│ └── run.js
├── _build/
│ └── sync_shared.py ← Copies shared/*.js into each agent's apps-script/ folder
└── README.md

Shared library (shared/)

Single source of truth for common code. Synced to each agent via _build/sync_shared.py (copies files with a "DO NOT EDIT" header). Key modules:

FileWhat it does
csv.jsCSV parsing and writing (RFC 4180 compliant, handles edge cases like embedded commas, BOM stripping)
rules.jsDeclarative rule engine. Rules are defined as spreadsheet rows, not code. Supports 8 rule types: not_blank, email_domain, one_of, equals, matches_map, required_if, blank_if, etc.
wage.jsWage-floor comparison logic. Handles federal FLSA (Fair Labor Standards Act), state, and local jurisdiction minimums. Supports hourly and salary thresholds.
exceptions.jsRoutes findings to named human reviewers based on exception type
auditlog.jsAppend-only logging designed to survive a DOL (Department of Labor) audit. Never deletes entries.
runwatch.jsWatchdog that detects missed runs (if a scheduled agent doesn't fire, it alerts)
jsonvalid.jsJSON Schema validation (no external libraries)
llm.jsBounded Claude integration. ONE call, strict JSON output, fail loud on any problem. Used only by minwage and leave agents.
paylocity.jsPaylocity OAuth 2.0 client (scaffolded but not live; API path was shelved in favor of CSV exports)

How agents are deployed

Deployment via clasp

clasp is a CLI tool that pushes code from your local repo to the Apps Script editor in Google's cloud.

# Push the reconciliation agent
clasp push -P product/mr-people-agents/recon/apps-script --force

Authenticated as cmprssn@madison-reed.com. Each agent has its own Apps Script project and its own spreadsheet.

Alternative: browser paste

For quick fixes, the gs-paste/ folders contain the same code formatted as .gs files that can be copied directly into the Apps Script editor in a browser.

Setup sequence for a new agent

  1. Create an Apps Script project under cmprssn@madison-reed.com
  2. Create a spreadsheet (same account); note its ID
  3. Set Script Properties:
    • AGENT_SPREADSHEET_ID — the spreadsheet ID
    • DRY_RUN = true (always start in dry run)
    • ANTHROPIC_API_KEY — only if the agent uses Claude (minwage, leave)
    • LLM_MODEL — optional (default: claude-sonnet-4-6)
  4. Push code via clasp
  5. Run setupTabs() once — creates the spreadsheet tabs (Config, Rules, Exception Queue, Audit Log, etc.)
  6. Fill the Config and rule tabs manually (the People team edits rows, not code)
  7. Test under dry run against sandbox data
  8. Run installTriggers() — schedules the cron jobs (time-based triggers)
  9. Flip DRY_RUN to false — deliberate, auditable action

How agents run

Agents run on time-based triggers (Google's version of cron jobs). Each agent has its own schedule:

Trigger typeWhat it means
WeeklyFires once a week on a specific day
BiweeklyFires weekly but the code checks if it's a payday (every 14 days from an anchor date)
MonthlyFirst of the month
DailyEvery day (used for watchdogs and return-to-work reminders)
WebhookHTTP POST to a web app URL (used by the reconciliation agent to respond to Paylocity events)
ManualTriggered by a human running a function in the Script editor

Data flow pattern (all agents)

External system (Paylocity, Legion, PulpStream)
↓ CSV exports (via SFTP, email, or manual download)
Google Drive folder (inbox)
↓ Agent reads files
Apps Script processes data against rules
↓ Findings written to spreadsheet
Exception Queue tab (human reviews)
↓ Human acts on findings
Audit Log tab (append-only, never deleted)

Dry run mode

Every agent defaults to DRY_RUN=true. In this mode:

  • All processing runs normally
  • Findings are logged to "Dry Run Findings" tabs
  • Nothing is written to the live Exception Queue
  • No emails are sent (logged to the Apps Script Logger instead)

This lets you validate an agent against real data before it goes live.

Design principles

These are closed decisions. They won't change.

1. Read-only on Paylocity, always

No agent writes to any payroll or HRIS system. Inputs are CSV exports. There is no write path anywhere in the codebase. If an employee's pay needs to change, a human does it through the normal Job Change workflow in Paylocity.

2. Flag, never fix

Every finding routes to a named human reviewer via the Exception Queue. The agent identifies the problem; the human decides what to do about it. Agents never auto-correct data.

3. AI proposes, human ratifies

When an agent uses Claude (only the minimum wage monitor and leave cert pre-screen do), the output lands in a staging state (pending-confirm or needs-human). A human reviews it and flips it to live or rejected. Nothing the AI writes goes directly into production.

4. Dry run by default

A fresh deploy logs everything and writes nothing. The deliberate flip from DRY_RUN=true to DRY_RUN=false is an auditable action. This prevents accidental production impact from a code push.

5. Run reliability is the failure mode

If an agent doesn't run when it should, that's a finding. The watchdog (daily heartbeat) detects missed runs and alerts. Absent or stale data exports are also flagged as findings. The worst thing isn't a wrong answer; it's silence.

6. Agent-owned Google account, least privilege

Everything runs under cmprssn@madison-reed.com or the mr-cmprssn service account (SA). Never personal credentials. The account has Company-Admin-No-SSN access in Paylocity (can see records but not Social Security numbers).

7. Zero external dependencies

All utilities live in the shared/ library. No npm packages, no transitive dependency risk. The codebase is self-contained.

8. DOL-survivable audit logs

Every agent maintains an append-only Audit Log tab. Every read, merge, comparison, finding, routing decision, and missed run is logged with timestamps. These logs are designed to withstand a Department of Labor audit. Retention policy: never delete (pending legal confirmation).

9. Spreadsheets as configuration

The People team edits spreadsheet rows to change rules, thresholds, routing, and config. Engineers edit code to change logic. This separation means the team can adjust what gets flagged without a code deploy, and engineers can update how the logic works without breaking the team's config.

Testing

Tests run in Node.js (not in Apps Script) using a custom harness that loads agent files into a vm context simulating Apps Script's flat global scope.

node tests/run.js

56 tests, zero external dependencies. Covers CSV edge cases, all 8 rule types, 4-dimension wage comparison, threshold state machine, exception deduplication, LLM failure handling, leave reconciliation, and pre-screen completeness checks.

Building new agents

When you build a new automation, follow this pattern:

  1. Pure logic in a *_lib.js file (testable, no I/O)
  2. I/O orchestration in Code.gs (reads from sheets/drive, calls the library, writes results)
  3. Config and rules in spreadsheet tabs (not hardcoded)
  4. Exception routing to named humans
  5. Audit logging for every action
  6. Dry run mode from the start
  7. Sync shared utilities via _build/sync_shared.py