CivicLoop by Ta-Tech Solutions Purpose: Specify every AI component - what it does, what goes in, what comes out, the model behind it, how it shows its work, and how it fails safely. Written so a skeptical County technical reviewer can satisfy themselves the AI is real and accountable, not theater.
A County panel has seen "AI-powered" on a dashboard before. CivicLoop holds every AI component to three rules:
There are six AI components plus four operational AI features (J - Autopilot orchestrator, K - Predictive issue forecast, L - Self-heal escalator, M - Loop channel persona). Each is specified below the same way. As of 2026-05-18, all ten are LIVE in production at https://civicloop-pgc.netlify.app.
Late-cycle additions (also live, also at the same URL):
Address-from-photo (Component G). When the resident attaches a photo, Claude vision reads any street signs / building numbers / intersection nameplates in the image and suggests a probable PG County address. EXIF GPS coords (read client-side, no AI call) feed the model as a hint and back-stop. A server-side looksLikePgCounty() validator rejects out-of-county results. Address field auto-fills above 0.5 confidence with a clear "Suggested from your photo. Edit if wrong." note. Fails safe.
Category-from-photo (Component H). Same Claude call also classifies the issue type from the photo against the 24 seeded category slugs. If the resident hasn't picked yet and confidence is above 0.7, we auto-pick. If they have picked and the model disagrees above 0.6, a one-tap "From your photo, this looks more like X" banner offers to switch. Same fail-safe path.
Voice playback confirmation (Component I). After the Web Speech API transcribes the resident's voice intake, the browser's speechSynthesis reads the transcript back in the resident's language ("Le escuche: hay un bache..." / "I heard: there's a pothole..."). Closes the audio loop so misheard reports are corrected instantly. No AI call - the model already heard it; the browser just speaks it back.
These three pair to enable the "one photo + one tap" demo: Maria snaps a photo of a pothole, the AI fills the category and the address from the same image, the voice playback confirms it back, and the resident submits in under 20 seconds.
The job: turn a resident's natural speech or text, in any language, into a structured, categorized, geo-located service request. This is the work a 311 call-taker used to do, available 24/7 in 35 languages.
| Inputs | Resident's voice (audio) or text; device location if shared; any photo; the conversation so far |
| Outputs | A draft Service Request: category, description (cleaned), suggested location, attachments, plus any clarifying question to ask next |
| Model | A large-language-model for understanding + dialogue; a speech-to-text layer for voice; both multilingual |
| Shows its work | The resident sees the transcription and the captured fields and can correct any of them before submitting - the AI never submits on the resident's behalf without confirmation |
| Fails safe | Low transcription confidence -> asks the resident to repeat or type. Cannot determine category -> offers a short pick-list. Cannot determine location -> asks for an address or cross-streets. At any point the resident can switch to a plain form. |
| Measured by | % of requests submitted without a phone call; % of intakes completed without falling back to the form |
Why it is not theater: it handles 35 languages by voice. No form does that. A County with a Language Access law (Document 02) cannot get this from a dropdown.
The job: classify the request and assign the correct department and priority - on the first pass, without a human triager - and explain why.
| Inputs | The structured request (category, description, location, attachments); the County's routing rules and SLA policies; the service area of the location |
| Outputs | Department assignment, priority, the SLA clock, and a plain-language rationale; a confidence score |
| Model | LLM classification working within the deterministic routing-rule catalogue - the rules are the floor, the AI handles the ambiguity rules cannot ("near a school," "safety near-miss," a description that spans two categories) |
| Shows its work | The rationale is shown to the agent in the Request workspace: "Routed to DPWT, High - resident reported a safety near-miss near a school." The agent can override with a reason; the override is recorded and feeds back |
| Fails safe | Low confidence -> routes by the deterministic rule and flags the request "AI unsure - confirm routing" for the receiving department. Never silently guesses. |
| Measured by | % correctly routed on first pass (no re-routing); re-route rate trending down over the pilot |
Why it is not theater: mis-routing and the "perennial challenge" of liaising between 311 and departments is a documented top failure mode (Document 02). Transparent, overridable AI routing is how departments come to trust it instead of fighting it.
The job: forecast which open requests will miss their deadline - early enough for a supervisor to act - instead of discovering breaches after they happen.
| Inputs | The request's category, priority, age, current status; the assigned department's current backlog and historical resolution times for that category; business calendar |
| Outputs | A breach-risk flag (low / elevated / likely) and the predicted breach date; surfaced on the supervisor's Escalations screen |
| Model | A lightweight predictive model trained on historical resolution patterns - not an LLM; this is a forecasting job, and a calibrated statistical model is the right tool and is explainable |
| Shows its work | The flag carries the factors: "Likely to breach - DPWT pothole backlog is 2x its 30-day average." A supervisor sees why, not just a red dot |
| Fails safe | With thin history (pilot start), it states low confidence and leans on simple age-vs-SLA math rather than a trained prediction. It never hides uncertainty. |
| Measured by | Breaches prevented (flagged + resolved in time) vs breaches that still occurred; prediction accuracy improving as history accumulates |
Why it is not theater: every surveyed competitor is reactive - they tell you about a breach after it happened. Prediction early enough to act is the open lane.
The job: catch an angry resident or a safety-critical situation and get it in front of a human before it becomes a formal complaint or a council call.
| Inputs | The resident's intake language and any comments they add; keywords and patterns indicating safety risk (injury, child, hazard, repeated failure) |
| Outputs | A sentiment reading and an escalation flag; flagged requests appear on the supervisor's Escalations screen with the reason |
| Model | LLM-based classification of tone and risk signals |
| Shows its work | The flag states the reason: "Escalated - resident describes a child nearly injured; frustration rising across three messages." |
| Fails safe | Biased toward over-flagging, not under-flagging - a false escalation costs a supervisor a glance; a missed one costs the County a complaint or worse. Borderline cases escalate. |
| Measured by | Escalations caught before they became formal complaints |
Why it is not theater: it converts the County's worst moments - the resident who would have called their council member - into a managed, in-system escalation a supervisor handles early.
The job: recognize when a new request is the same issue as an existing open one, so the County works it once and every affected resident is still kept informed.
| Inputs | The new request's category, location, description; open requests nearby in the same category |
| Outputs | A duplicate-likelihood score and the candidate parent request; surfaced to the agent to confirm |
| Model | Geospatial proximity + category match + LLM similarity on the descriptions |
| Shows its work | The agent sees the candidate parent and why it matched: "Likely duplicate - same category, 40m away, filed 2 days ago." The agent confirms or rejects |
| Fails safe | It only ever suggests; a human confirms the merge. On merge, both residents stay subscribed to the parent's updates - nobody is dropped |
| Measured by | Duplicate requests merged; reduction in redundant work orders |
Why it is not theater: duplicate reporting is a documented resident and city frustration. Detecting it without dropping any resident's connection to the outcome is the careful version.
The job: watch the whole stream of requests across the County and surface patterns no human is staffed to notice - a cluster forming, a category spiking, a neighbourhood going quiet, an SLA sliding - and put the pattern in front of the right leader before it becomes a crisis.
| Inputs | The full request stream: category, location, time, status, resolution time, language, channel - continuously |
| Outputs | Detected trends pushed to the Director Dashboard and to the relevant department head: emerging geographic clusters, category spikes/drops vs baseline, SLA drift, demand shifts by time-of-day or season, language-demand changes |
| Model | Time-series anomaly detection + geospatial clustering, with an LLM layer that turns a detected pattern into a plain-language briefing |
| Shows its work | Each trend card states the evidence: "Road-hazard reports on the Annapolis Road corridor are up 240% over the 90-day baseline - 14 requests, 11 within 600m." With a chart, not just a sentence |
| Fails safe | It flags patterns; it does not act on them. A human decides what a trend means. Thin-data periods are stated as low confidence. |
| Measured by | Trends surfaced that led to a proactive County action (a resurfacing project, a staffing shift) before reactive complaints piled up |
Why it is not theater: weak analytics is a documented failure across every surveyed competitor (Document 02). Trend detection - the system telling leadership what to look at - is a level past the dashboards everyone else ships.
The job: be the single, governable hand that decides how much
the AI does on its own at intake. Driven by counties.autopilot_level
(off / route / full); written by the County Admin on /admin;
audited on every flip.
| Inputs | A newly submitted Service Request; counties.autopilot_level for the tenant; the routing-rule catalogue (Component B); the per-department workload (count of open assigned + in_progress per agent in that dept) |
| Outputs | At off: nothing - the request waits for a human triager. At route: a department assignment + priority via Component B. At full: a department assignment + priority, a specific agent assignment (the lightest-loaded agent in that department), and the "assigned" resident notification - all in one transaction |
| Model | Not a model in itself - an orchestrator on top of Components B and the lightest-load calculation. The "intelligence" is the deterministic policy plus the governable dial |
| Shows its work | Every step is logged with actor='ai' and an AUTOPILOT: prefix on the request timeline. The County Admin can read the whole automation chain on any request in the agent workspace |
| Fails safe | At full, if the lightest-load calculation returns no eligible agent (the department is empty), it falls back to route behavior and flags the request "Autopilot fell back to manual assign - no eligible agent." It never silently drops a request and it never assigns to an inactive or off-duty agent |
| Measured by | Time-to-first-touch (submit -> ASSIGNED) at full vs route vs off; reassignment rate after Autopilot assigns (should not exceed manual baseline); % of inbound that needed human triage at each dial position |
Why it is not theater: the County controls the level of delegation, in public, on one dial. No rival 311 system lets a County name its own automation level this directly. It is also the demo's closing move (Document 13).
The job: project, per (category, council_district), the
expected request count for the next 7 days, with a confidence score,
so a Director can staff and route ahead of demand rather than behind
it. Director-triggered via the "Run forecast now" button on
/dashboard; persists to the predicted_issues table.
| Inputs | The last 12 weeks of resolved + open Service Requests, joined to category and council_district; the current week's running counts (for the seasonality factor) |
| Outputs | One row per (category, council_district) per forecast run: expected_count for the next 7 days, a confidence in [0,1], the computed_at timestamp, horizon_days = 7 |
| Model | A 12-week trailing mean per (category, council_district), scaled by a recent-seasonality factor (the ratio of the last 4 weeks' count to the 12-week mean). Confidence is a function of sample size and inter-week variance. Not an LLM - a statistical forecast is the right tool, and it is explainable, fast, and re-runnable |
| Shows its work | The forecast panel on the Director dashboard shows the number, the confidence, the underlying 12-week mean, and the recent-week trend. The Director sees why a number is high, not just that it is |
| Fails safe | Thin-history (category, council_district) pairs (under a configurable sample-size floor) report low confidence and a "more data needed" flag rather than a spuriously precise number |
| Measured by | Forecast vs actual over rolling 4-week windows; absolute % error, trending down as history accumulates |
Why it is not theater: every rival has dashboards that say what happened. This forecast says what is about to happen, with the math visible, on a button the Director presses. It is the only operational forecast in the surveyed market (Document 02).
The job: scan open requests for SLA breach or near-breach,
reassign to a supervisor in the same department, bump priority,
post a heads-up in the department channel, and notify the resident -
all in one idempotent pass. Reachable as a manual "Run now" on
/admin and as a cron at /api/cron/self-heal (optionally gated by
SELFHEAL_CRON_TOKEN).
| Inputs | All open requests (assigned, in_progress, needs_info) with sla_due_at past or within the near-breach window; per-department supervisor list; the department's channel |
| Outputs | For each eligible request: a reassignment to a supervisor, a priority bump, a @first-name heads-up message in the department channel (via Component J's channel writer), a resident notification, and a Request Event with the AUTOPILOT: heal prefix |
| Model | Deterministic policy, not a model. The intelligence is the choice of what to do, atomically, idempotently, with a 6-hour cooldown per request so the same one is never escalated twice in the same window |
| Shows its work | Every action writes a Request Event and an Audit Log row, both attributed to actor='ai' with the heal prefix. The resident-facing notification says plainly what changed and who now owns it |
| Fails safe | If the department has no available supervisor, the request is escalated to the 311 Director and the failure is logged - it is never silently left to breach further. Idempotent on the 6h cooldown so an over-eager cron cannot spam residents |
| Measured by | % of would-have-breached requests caught before breach; % of breaches healed within the heal cooldown; supervisor accept rate on heal-driven reassignments |
Why it is not theater: breach prevention is what the prediction component (Component C) flags. Self-heal is what acts on the flag without waiting for a human to notice. It is the operational twin of Autopilot, applied to the moment a request goes off the rails.
The job: be the AI voice in the department channels. When any
staff member types @loop in a channel, Loop replies in the same
channel, signed "Loop AI", grounded in a freshly computed county
snapshot. Different from the broader role co-pilots (Section 7c) -
Loop is the channel-facing voice. Slash commands are deterministic
and never call Loop.
| Inputs | The @loop-tagged message; the parent channel and any thread it sits in; a fresh county snapshot computed at request time (open count, breaches, last-24h status mix); the recent message history of the channel |
| Outputs | A short, plain-language reply in the channel, attributed to the loop system identity (Document 04) |
| Model | Claude Haiku, prompted with the snapshot + the channel context, with a strict no-PII guardrail (never name a resident, never quote a resident comment verbatim) |
| Shows its work | The reply links any CP-... it cites into /console/[requestNumber]. Loop's source numbers (open count, breaches, etc.) are stated, not assumed |
| Fails safe | If the snapshot computation fails, Loop replies "I cannot pull a fresh snapshot right now - try /breaches or /open instead." Slash commands (/help, /open, /breaches, /summary CP-...) are deterministic so the channel is never dependent on the LLM being up |
| Measured by | Staff usage of @loop (a signal it is genuinely useful, not theater); slash-command success rate (must be 100%, since they are deterministic) |
Why it is not theater: the channels are where the staff already talk. Loop is in that room, not in a separate AI surface a staffer has to remember to open. It also rides on the same deterministic slash-command floor (Document 02 anti-lock-in), so the channel works even with the model down.
Not a sixth model, but worth specifying because it is where the AI's language capability meets the resident. On every lifecycle transition (Document 03), the notification engine:
This is what closes the black hole - and it is automatic, no staff action required.
The five components above (A-F) are the system's intelligence. The agentic layer is what makes that intelligence act. CivicLoop is not a set of screens a human drives - it is an event-driven system where autonomous agents do work the moment something happens, so the humans are freed to do the judgement work only humans can do. This is "life made easy": the system does the running-around.
Everything that happens in CivicLoop is an event - a request submitted, a status changed, an SLA clock crossing a threshold, a comment added, a photo attached, a resident replying, a trend detected. Every event can trigger one or more agents - small, autonomous, single-purpose workers that run without anyone clicking a button.
EVENT AGENT(S) THAT FIRE
---------------------------- ---------------------------------
Request submitted -> Classifier agent, Router agent,
Duplicate-check agent, Resident
acknowledgement agent
Request routed -> SLA-clock agent, Department-notify
agent
Status -> ASSIGNED -> Resident-notify agent ("crew
assigned")
SLA clock hits 60% elapsed -> Breach-prediction agent; if
"likely", Supervisor-escalation
agent
Angry/safety language seen -> Sentiment agent -> Escalation agent
Status -> RESOLVED -> Proof-check agent (blocks close
without a photo), Resident-notify
agent (with before/after)
No resident reply in NEEDS
INFO for N days -> Reminder agent, then Auto-close-
with-notice agent
Trend detected -> Briefing agent -> pushes a card to
the Director + department head
Request closed -> Open-data export agent, MyPGC feed
agent
Nightly -> Trend-detection sweep, SLA-health
digest, stale-request sweep
The County staff member does not orchestrate any of this. They open their queue and the work that needs human judgement is there, teed up, with the routing done, the resident already informed, the SLA risk already flagged. The agents handled the rest.
The Ta-Tech engine already runs an event + cron automation engine in production - it powers more than twenty autonomous automations on the healthcare platform (a daily director brief, capacity alerts, critical-result paging within sixty seconds, missed-step flags, license-lapse blocks, and more), built as a hybrid of event-triggered and scheduled agents. CivicLoop's agentic layer is that same engine with 311 events and 311 agents defined on it. The pattern - events fire, agents act, every action is recorded as an AI Decision or Audit Log entry - is proven.
Every agent action is still recorded and attributable (Section 8). An agent that notifies a resident, escalates to a supervisor, or auto-closes a stale request writes an audit row attributed to the "ai" / "system" identity, with its trigger and its rationale. Agentic does not mean unaccountable - it means the busywork is automated and the trail is complete.
The event agents in 7b are single-purpose: they fire, they do one thing, they finish. Role agents are different. Each strategic role in CivicLoop (Document 04) gets a persistent AI co-pilot assigned to it - an agent that continuously watches that role's domain, surfaces what matters, drafts the actions that role would take, and - within bounded, audited limits - can act on the role-holder's behalf.
Every County worker, from the frontline agent to the Director, has an AI working alongside them. That is "life made easy" made concrete.
| Role | Role agent (working name) | Continuously watches | Drafts / proposes | Can do autonomously (bounded) |
|---|---|---|---|---|
| Resident | Personal assistant | The resident's own requests | Nothing - it serves the resident | Files on the resident's spoken instruction, tracks, and chases the County for an update if a request goes quiet |
| Agent | Frontline co-pilot | The agent's queue | Pre-drafts the reply to the resident; suggests resolution steps from similar resolved requests; groups nearby open requests into one field trip | Drafts only - the agent sends. Auto-attaches relevant history to a request |
| Supervisor | Team agent | The whole department queue + agent workloads | Proposes reassignments when one agent is drowning; surfaces the escalations that need a human now | Rebalances within a configurable load threshold; above it, proposes and waits |
| Department Head | Department agent | The department's SLA health and trend lines | Drafts the staffing or budget case - "you need another crew on the Annapolis corridor" - with the data behind it | Reporting and drafting only; no autonomous action |
| 311 Director | The briefing agent (flagship) | Every request, every department, county-wide | The daily county briefing; the trend cards ("here is what to look at today"); the cross-department patterns | Compiles and pushes briefings and trend cards; proposes cross-department escalations |
| County Admin | System agent | Configuration, integration health, the audit log | Flags config drift, integration failures, audit anomalies, routing rules that the AI keeps having to override | Alerts only; configuration changes are always human |
The Ta-Tech engine already runs named role agents in production on the healthcare platform - a director's briefing agent, a clinical co-pilot, a lab agent, a pharmacy-guardian agent, each tied to a role, each watching, drafting, and acting within bounds. CivicLoop's role agents are that proven pattern, re-pointed at county work. The names above are working placeholders - Ta-Tech's house style is to name agents after real people, and the County (or Ta-Tech) names them for real before launch.
| Safeguard | How |
|---|---|
| Every decision recorded | Every component writes an AI Decision row: inputs, output, confidence, rationale, model version. Document 03. |
| Every decision attributable | AI actions are attributed to the "ai" system identity in the audit log - never to a person, never invisible. Document 04. |
| Humans can always override | Routing, duplicates, escalations - the AI proposes, a human can override, and the override is recorded and feeds back. |
| Confidence is surfaced, not hidden | Low-confidence outputs say so and fall back to rules or humans. The system never presents a guess as a certainty. |
| No automated decision about a person | CivicLoop's AI routes requests and predicts workload. It does not make eligibility, enforcement, or benefit decisions about residents. (When Phase 2 / HHS introduces risk scoring about people, that is a separate, human-in-the-loop design - explicitly out of v1 scope.) |
| Model versioning | Each AI Decision records the model version, so behavior changes are traceable. |
All six components and the agentic layer are real in the demo, on real demo data:
The standard holds: each one does real work, shows its work, and fails safe. If a panelist probes any of them, it survives the probe.
Next: 08 - Integrations & Data.