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.comservice 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:
| File | What it does |
|---|---|
csv.js | CSV parsing and writing (RFC 4180 compliant, handles edge cases like embedded commas, BOM stripping) |
rules.js | Declarative 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.js | Wage-floor comparison logic. Handles federal FLSA (Fair Labor Standards Act), state, and local jurisdiction minimums. Supports hourly and salary thresholds. |
exceptions.js | Routes findings to named human reviewers based on exception type |
auditlog.js | Append-only logging designed to survive a DOL (Department of Labor) audit. Never deletes entries. |
runwatch.js | Watchdog that detects missed runs (if a scheduled agent doesn't fire, it alerts) |
jsonvalid.js | JSON Schema validation (no external libraries) |
llm.js | Bounded Claude integration. ONE call, strict JSON output, fail loud on any problem. Used only by minwage and leave agents. |
paylocity.js | Paylocity 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
- Create an Apps Script project under
cmprssn@madison-reed.com - Create a spreadsheet (same account); note its ID
- Set Script Properties:
AGENT_SPREADSHEET_ID— the spreadsheet IDDRY_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)
- Push code via clasp
- Run
setupTabs()once — creates the spreadsheet tabs (Config, Rules, Exception Queue, Audit Log, etc.) - Fill the Config and rule tabs manually (the People team edits rows, not code)
- Test under dry run against sandbox data
- Run
installTriggers()— schedules the cron jobs (time-based triggers) - Flip
DRY_RUNtofalse— 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 type | What it means |
|---|---|
| Weekly | Fires once a week on a specific day |
| Biweekly | Fires weekly but the code checks if it's a payday (every 14 days from an anchor date) |
| Monthly | First of the month |
| Daily | Every day (used for watchdogs and return-to-work reminders) |
| Webhook | HTTP POST to a web app URL (used by the reconciliation agent to respond to Paylocity events) |
| Manual | Triggered 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:
- Pure logic in a
*_lib.jsfile (testable, no I/O) - I/O orchestration in
Code.gs(reads from sheets/drive, calls the library, writes results) - Config and rules in spreadsheet tabs (not hardcoded)
- Exception routing to named humans
- Audit logging for every action
- Dry run mode from the start
- Sync shared utilities via
_build/sync_shared.py