Tech Direct Support  ·  Solution Architecture

TDS CoPilot — Agent Architecture
& Workflow Design

Autonomous shift-to-cash automation for the security industry: request intake, rostering, guard allocation, client confirmation, billing, attendance, timesheet approval and executive reporting — with human control points at every point of risk.

Prepared for board / executive review  ·  11 August 2026  ·  Derived from the 13-point TDS CoPilot brief

10 agents — 1 orchestrator + 9 specialists

Your 13 requirements decompose cleanly into 9 workflow segments. Each segment is a distinct decision domain with its own data, its own rules and its own failure mode — so each gets one specialist agent. A tenth agent, the Orchestrator, owns the shift state machine, retries and handoffs so no specialist has to know about any other.

Workflow segments9Each with its own diagram below
Specialist agents9One per segment, single-purpose
Orchestrator1State, retries, handoffs, audit
Human checkpoints10Named, gated, logged
Shared services3Not agents — integration, gate, log
Phase 1 MVP5Merged build — see Build Plan

Why not fewer agents?

A single "do everything" agent cannot be tested, tuned or trusted. Your brief demands 100% accuracy — that is only achievable when each step has a narrow scope, a measurable pass rate, and can be independently rolled back. Fatigue-rule logic and invoice logic failing together is an outage; failing separately is a ticket.

Why not more?

Every extra agent adds a handoff, and handoffs are where state gets lost. Anything that shares the same data, the same system-of-record and the same approver belongs in one agent. Ten is the point where each agent owns exactly one system-of-record write and one class of decision.

Agent Register

Ten agents, each with one job, one primary system-of-record, and a defined autonomy level.

#AgentOwnsPrimary systemsAutonomyBrief ref
0Orchestrator AgentShift state machine, sequencing, retries, timeouts, handoffs, full audit trailInternal store, event logAutonomous12
1Intake AgentReads inbound requests, classifies static / adhoc / core hours, extracts structured shift dataOutlook, WhatsApp, client portalsSemi — confidence gate1, 2
2Roster AgentResolves customer + site, creates the shift on the correct platformGuardhouse, FieldService, HumanforceGated on new site/customer2, 3
3Allocation AgentRanks guards by site history, licence validity, availability; enforces the 36–48 hr fortnight fatigue ruleRostering platforms, licence registerSemi — gated on supplier fill4, 5
4Engagement AgentMulti-channel offers, first-accept-wins locking, escalation waves, offer withdrawalWhatsApp, email, Humanforce / Guardhouse / FieldService pushAutonomous5, 6
5Client Comms AgentReplies on the original email thread with guard details, ETA and shift referenceOutlookSemi — per-client disclosure rules6
6Commercials AgentClient billing profile, PO raise & send, invoice generation after approvalAccounting / invoicing platform, OutlookGated on new client + variance7
7Attendance AgentMonitors clock in / out, late and no-show detection, triggers urgent backfillGuardhouse, FieldService, HumanforceSemi — human on no-show11
8Timesheet AgentRostered vs actual reconciliation, auto-approves clean sheets, classifies exceptionsRostering platformsSemi — exceptions to approver8, 9
9Insights AgentDaily shift volume, $ value, invoicing pipeline, timesheet error queue, fatigue watchlistEvent log, all platformsAutonomous10

Three shared services — deliberately not agents

Integration Layer

One adapter per platform — Guardhouse, FieldService, Humanforce, Outlook, WhatsApp Business, accounting. Agents never call a vendor API directly, so a vendor change touches one adapter, not nine agents.

Human Checkpoint Service

A single approval queue and notification path used by all 10 gates. One inbox for your ops team, not ten different prompts in ten different places.

Audit & Event Log

Every agent action, every human decision, every API response — immutable and timestamped. This is what makes the "100% accuracy" claim defensible to a client, an auditor, or a court. It is also the data source for the dashboard.

Master Flow — End to End

The board view: one shift request, from client email to cash and dashboard.

flowchart TD
  subgraph L1[LAYER 1 - DEMAND]
    A["S1 Intake and Interpretation"] --> B["S2 Site Resolution and Shift Creation"]
  end
  subgraph L2[LAYER 2 - SUPPLY]
    C["S3 Guard Matching and Fatigue Compliance"] --> D["S4 Offer and Acceptance"]
  end
  subgraph L3[LAYER 3 - COMMIT]
    E["S5 Client Confirmation"] --> F["S6a Billing Profile and PO"]
  end
  subgraph L4[LAYER 4 - DELIVERY]
    G["S7 Attendance and Clock In Out"] --> H["S8 Timesheet Reconciliation"]
  end
  subgraph L5[LAYER 5 - CASH AND VISIBILITY]
    I["S6b Invoice Issue"] --> J["S9 Command Dashboard"]
  end
  B --> C
  D --> E
  F --> G
  H --> I
  O(["Agent 0 - Orchestrator
state, retries, handoffs, audit"]) -.-> L1 O -.-> L2 O -.-> L3 O -.-> L4 O -.-> L5 HC[/"Human Checkpoint Service
10 named gates"/] -.-> L1 HC -.-> L2 HC -.-> L3 HC -.-> L4

Solid arrows are the shift lifecycle. Dotted lines are cross-cutting control. Note that invoicing is split: PO before delivery, invoice after timesheet approval.

Segment Diagrams

S1

Intake & Interpretation

Turn an unstructured client email into a validated, machine-readable shift request.

Agent1 — Intake Agent
TriggerNew mail in shared mailbox
Human gateCP1 — low-confidence parse
Brief refPoints 1 & 2
flowchart TD
  A["Client request lands in Outlook shared mailbox"] --> B{"Is this a shift or coverage request?"}
  B -->|No| Z["Route to human inbox and stop"]
  B -->|Yes| C["Intake Agent classifies request type"]
  C --> C1["Static guard on site"]
  C --> C2["Adhoc one off work"]
  C --> C3["Ongoing core hours"]
  C1 --> D["Extract fields"]
  C2 --> D
  C3 --> D
  D --> E["Customer, site, address, date, start, finish, headcount,
licence class, rate, PO reference, client contact"] E --> F{"Extraction confidence above threshold
and no required field missing?"} F -->|No| G[/"CHECKPOINT 1
Human confirms or corrects parsed request"/] F -->|Yes| H["Create Shift Request record with unique TDS reference"] G --> H H --> I["Acknowledge receipt to client on the same thread"] I --> J["Hand to Roster Agent"]

Failure modes handled

  • Ambiguous dates — "next Tuesday night" resolved against client timezone and shift-crossing-midnight rules, then confirmed if still ambiguous.
  • Bulk requests — one email containing a week of shifts is split into N separate shift records, each independently tracked.
  • Amendments & cancellations — replies on an existing thread are matched to the original TDS reference, not treated as new demand.
S2

Site Resolution & Shift Creation

Place the shift against the correct customer and site on the platform that client uses — never guess.

Agent2 — Roster Agent
TriggerValidated shift request
Human gatesCP2 new customer · CP3 new site
Brief refPoints 2 & 3
flowchart TD
  A["Validated Shift Request"] --> B["Identify which platform this client uses"]
  B --> C["Guardhouse / FieldService / Humanforce"]
  C --> D{"Customer record exists?"}
  D -->|No match| E[/"CHECKPOINT 2
Approve creation of new customer"/] D -->|Fuzzy match| F[/"CHECKPOINT 2b
Confirm this is the right customer"/] D -->|Exact match| G{"Site record exists under that customer?"} E --> G F --> G G -->|No| H[/"CHECKPOINT 3
Approve creation of new site"/] G -->|Yes| I["Create shift via platform API"] H --> I I --> J{"API call successful?"} J -->|No| K["Retry 3 times with backoff"] K --> L{"Still failing?"} L -->|Yes| M[/"Escalate to ops - platform outage"/] L -->|No| N["Write platform shift ID back to TDS record"] J -->|Yes| N N --> O["Hand to Allocation Agent"]
Design decision: the agent is never permitted to auto-create a customer or site. A wrongly created site corrupts billing, compliance reporting and every future match — the cost of a mistake is far higher than the cost of an approval click. This directly implements point 3 of your brief.
S3

Guard Matching & Fatigue Compliance

Find who has done this shift before, and confirm they can legally and safely do it again.

Agent3 — Allocation Agent
TriggerShift created on platform
Human gateCP4 — supplier fill approval
Brief refPoints 4 & 5
flowchart TD
  A["Shift live on platform"] --> B["Pull shift history for this site and this client"]
  B --> C["Rank candidates by site familiarity and reliability score"]
  C --> D["Hard filter - licence current, inductions valid,
no clash, right qualification class"] D --> E["Fatigue calculation - hours already rostered this fortnight plus this shift"] E --> F{"Total within the 36 to 48 hour threshold?"} F -->|No| G["Exclude guard and log overtime breach reason"] F -->|Yes| H["Add to TIER 1 - direct TDS guards"] G --> I{"Any Tier 1 candidates remaining?"} H --> I I -->|Yes| M["Build ranked offer list"] I -->|No| J["Escalate to TIER 2 - approved supplier guards"] J --> K{"Is supplier fill pre-approved for this client?"} K -->|Yes| M K -->|No| L[/"CHECKPOINT 4
Approve supplier fill and margin impact"/] L --> M M --> N["Hand ranked list to Engagement Agent"]

Why this is the highest-risk agent

  • Fatigue breaches are a regulatory exposure, not an admin error. The fortnight window must be calculated across all platforms a guard appears on, not one.
  • Expired licences must be a hard block with no override path available to the agent — only to a named human.
  • Margin — Tier 2 supplier fill changes the economics of the shift. That is a commercial decision, so it is gated.
S4

Offer & Acceptance

Reach the guard where they actually are, and lock the shift the moment someone says yes.

Agent4 — Engagement Agent
TriggerRanked offer list
Human gateCP5 — shift cannot be filled
Brief refPoints 5 & 6
flowchart TD
  A["Ranked offer list"] --> B["Send offer - wave 1 to top candidates"]
  B --> C["WhatsApp message"]
  B --> D["Email"]
  B --> E["In app push - Humanforce, Guardhouse, FieldService"]
  C --> F{"Response inside the SLA window?"}
  D --> F
  E --> F
  F -->|Accepts| G["First accept wins - lock the shift immediately"]
  F -->|Declines| H["Mark declined and move down the list"]
  F -->|No response| I["Wave 2 - widen the pool and shorten the window"]
  H --> J{"Candidates remaining?"}
  I --> J
  J -->|Yes| B
  J -->|No| K[/"CHECKPOINT 5
Unfilled shift - ops decides: supplier, premium rate,
or advise client"/] G --> L["Withdraw all outstanding offers for this shift"] L --> M["Assign guard across every relevant platform"] M --> N["Confirm to guard - site, address, ETA, contact, special instructions"] N --> O["Hand to Client Comms Agent"]
Race condition — the single most common failure in shift automation. Two guards accepting the same shift within seconds is not an edge case; it is a weekly event. The lock must be atomic at the shift record, and withdrawal messages must go out immediately so the second guard is never left believing they are rostered.
S5

Client Confirmation

Close the loop on the original email thread with exactly what that client is entitled to see.

Agent5 — Client Comms Agent
TriggerGuard assigned and locked
Human gateCP6 — undefined disclosure rules
Brief refPoint 6
flowchart TD
  A["Guard assigned"] --> B["Load client communication profile"]
  B --> C{"Profile defined for this client?"}
  C -->|No| D[/"CHECKPOINT 6
Confirm what this client wants disclosed
and who is copied"/] C -->|Yes| E["Assemble confirmation pack"] D --> E E --> F["Guard name and licence number"] E --> G["Shift date, start, finish, ETA"] E --> H["Site and TDS shift reference"] E --> I["Escalation contact"] F --> J["Reply on the ORIGINAL email thread - preserve subject and recipients"] G --> J H --> J I --> J J --> K["Log confirmation against the shift record"] K --> L["Trigger Commercials Agent"]
Privacy note: some clients contractually receive guard names and licence numbers; others must not, and some jurisdictions restrict it. The disclosure profile per client is a compliance control, not a formatting preference — which is why it is gated the first time.
S6

Commercials — PO & Invoicing

Every client bills differently. The agent learns each profile once, from a human, then runs it.

Agent6 — Commercials Agent
TriggerConfirmation sent · then timesheet approved
Human gatesCP7 billing profile · CP8 variance
Brief refPoint 7
flowchart TD
  A["Shift confirmed to client"] --> B["Load client billing profile"]
  B --> C{"Billing profile on file?"}
  C -->|No| D[/"CHECKPOINT 7
Human defines: PO required, rate card, cycle,
consolidation, portal or email, approver"/] C -->|Yes| E{"PO required before the shift is worked?"} D --> E E -->|Yes| F["Raise PO and send to client per profile"] E -->|No| G["Flag shift as bill on completion"] F --> H["Store PO number on the shift record"] G --> H H --> I(["PARK - wait for timesheet approval"]) I --> J["S8 returns an approved timesheet"] J --> K["Generate invoice from approved actual hours"] K --> L{"Variance against quoted value above tolerance?"} L -->|Yes| M[/"CHECKPOINT 8
Approve invoice before issue"/] L -->|No| N["Auto issue invoice per client cycle"] M --> N N --> O["Write to accounting platform and dashboard"]
This is the sequencing correction from point 12 of your brief. Your original order raises the invoice at step 7, before the guard has worked and before timesheets are approved at steps 8–9. That would invoice unworked or incorrect hours. The fix: PO is raised at commit (it authorises the work), the invoice is generated only after timesheet approval (it bills the actual). One agent, two triggers, a park state in between.
S7

Attendance — Clock In / Clock Out

The shift is only delivered when a guard is physically on site. This agent watches that in real time.

Agent7 — Attendance Agent
TriggerShift start window opens
Human gateCP9 — no-show
Brief refPoint 11
flowchart TD
  A["Shift start approaching"] --> B["Pre shift reminder to guard - T minus 2 hours"]
  B --> C{"Clocked in inside the grace window?"}
  C -->|Yes| D["Mark on site and verify geolocation against site"]
  C -->|No| E["Auto contact guard - WhatsApp and app prompt"]
  E --> F{"Guard responds and is en route?"}
  F -->|Yes| G["Notify client of revised ETA"]
  F -->|No| H[/"CHECKPOINT 9
NO SHOW - ops authorises urgent backfill"/] H --> I["Re inject into S3 Allocation as urgent priority"] I --> J["Notify client of replacement guard"] D --> K["Monitor mid shift welfare checks and patrol scans"] G --> K K --> L{"Clock out recorded at shift end?"} L -->|Yes| M["Close attendance record with actual hours"] L -->|No| N["Flag missing clock out as exception"] M --> O["Hand to Timesheet Agent"] N --> O

Why attendance must sit before timesheets

Your brief lists clock in/out at point 11, after timesheet approval at points 8–9. In practice attendance data is the source of truth a timesheet is reconciled against — so it must be captured first. This is the second re-sequencing correction under point 12.

S8

Timesheet Reconciliation & Approval

Auto-approve what is clean. Escalate what is not, with the error already diagnosed.

Agent8 — Timesheet Agent
TriggerShift completed
Human gateCP10 — timesheet exception
Brief refPoints 8 & 9
flowchart TD
  A["Shift completed"] --> B["Compare rostered versus actual against attendance record"]
  B --> C{"Variance inside this client's tolerance?"}
  C -->|Yes| D["Auto approve on the client's platform"]
  C -->|No| E["Classify the exception"]
  E --> F["Missing clock in or clock out"]
  E --> G["Overtime or extended hours"]
  E --> H["Wrong site or wrong guard clocked on"]
  E --> I["Break, allowance or penalty rate mismatch"]
  F --> J[/"CHECKPOINT 10
Approver is shown the error, the evidence
and the options: fix, approve as is, reject"/] G --> J H --> J I --> J J --> K["Apply the human decision on the platform"] K --> L["Record decision and reason in the audit log"] D --> M["Release to Commercials Agent for invoicing"] L --> M M --> N["Feed exception pattern back to Insights Agent"]
Compounding value: exception patterns feed back into the dashboard. If one site produces 40% of your timesheet errors, that is an operational fix — a broken scanner, an unclear geofence, a guard who needs retraining — and CoPilot surfaces it rather than absorbing it silently.
S9

Command Dashboard

The single screen that tells you whether the business ran today.

Agent9 — Insights Agent
TriggerContinuous · daily digest
Human gateNone — read only
Brief refPoint 10
flowchart TD
  A["Every agent writes to the shared event log"] --> B["Insights Agent aggregates"]
  B --> C["Shifts today - filled, unfilled, in progress, completed"]
  B --> D["Daily and month to date dollar value"]
  B --> E["Invoices pending, issued, overdue"]
  B --> F["Timesheets in error, grouped by client and by cause"]
  B --> G["Guard fatigue and overtime watchlist"]
  B --> H["Unfilled shift risk for the next 7 days"]
  B --> I["Autonomy score - percent completed without human touch"]
  C --> J["Live operations dashboard"]
  D --> J
  E --> J
  F --> J
  G --> J
  H --> J
  I --> J
  J --> K["Daily digest to leadership"]
  J --> L["Exception queue to ops"]
The autonomy score is your board metric. It is the percentage of shifts that complete the full lifecycle without a human touch. It starts low by design — every checkpoint fires while the system learns your clients — and rises as billing profiles, site records and disclosure rules get captured. That curve is the ROI story.

Re-sequencing — Point 12 Resolved

Your brief asked for the order to be corrected where it does not flow. Three changes.

#Your original orderCorrected orderWhy it matters
1Step 7 — invoicing runs immediately after client confirmation, before the shift is workedSplit: PO at commit, invoice after timesheet approvalPrevents invoicing hours that were never worked or that changed on the night. Removes credit notes and client disputes.
2Step 11 — clock in/out checked last, after timesheet approvalAttendance moves before timesheet approvalAttendance data is the evidence a timesheet is reconciled against. Approving first and checking later is backwards.
3Step 4–5 — guard history, then overtime checkMerged into one gate: match then hard-filter on licence, availability and fatigue in a single passAvoids offering a shift to a guard who was always ineligible — a common source of guard frustration and wasted cycle time.

Human Checkpoint Map

Ten named gates. Each one exists because the cost of an agent error at that point exceeds the cost of a human click.

GateSegmentFires whenDecision requiredRetires when
CP1S1 IntakeParse confidence below threshold or required field missingConfirm or correct the parsed requestNever fully — threshold tightens with volume
CP2S2 RosterCustomer not found or only fuzzy-matchedApprove new customer / confirm matchAs customer master data matures
CP3S2 RosterSite not found under that customerApprove new site creationAs site register matures
CP4S3 AllocationNo compliant direct guard — supplier fill neededApprove supplier and margin impactPer-client standing approval can be set
CP5S4 EngagementOffer list exhausted, shift still unfilledSupplier, premium rate, or advise clientNever — commercial judgement call
CP6S5 Client CommsNo disclosure profile for this clientDefine what is shared and with whomOnce per client, permanently
CP7S6 CommercialsNo billing profile for this clientDefine PO rules, rate card, cycle, approverOnce per client, permanently
CP8S6 CommercialsInvoice value varies beyond toleranceApprove, adjust or hold the invoiceNever — financial control
CP9S7 AttendanceGuard has not clocked in and is unreachableAuthorise urgent backfill and client notificationNever — service failure event
CP10S8 TimesheetRostered vs actual variance outside toleranceFix, approve as-is, or rejectTolerances widen as data quality improves
Board framing: CP2, CP3, CP6 and CP7 are one-time-per-entity gates — they disappear as your master data fills in. CP5, CP8 and CP9 are permanent by design — those are commercial, financial and service-failure decisions that should never be delegated to software.

Build Plan — Phasing the 10 Agents

You do not build ten agents at once. You build the spine, prove accuracy, then extend.

PhaseAgents liveWhat it provesExit criteria
15 agents — Orchestrator + merged Intake/Roster + merged Allocation/Engagement + Attendance + InsightsA shift request can go from email to a filled, worked shift without manual rostering95%+ of requests parsed and rostered correctly · zero fatigue breaches · one platform integrated end to end
2+2 — split Client Comms out, add Timesheet AgentThe client loop closes automatically and clean timesheets self-approveTimesheet auto-approval rate above 70% · client confirmations sent within SLA
3+1 — Commercials Agent with PO and invoice splitShift-to-cash is complete; billing profiles captured for top clientsBilling profiles on file for clients covering 80% of revenue · invoice accuracy 100%
4Full 10 — de-merge Phase 1 agents, add remaining platformsMulti-platform, multi-client scale with independent agent tuningAll three platforms live · autonomy score trending above 80%

What we still need from you to lock the design