dim_agent
through the adminAgents tRPC router and renders one row per agent,
human or LLM. From here you filter the roster, reassign people in bulk,
export a CSV, and open a detail page. The New agent wizard writes
into the same table.
Everything on the screen is scoped to the active org
(tenant.activeOrg.id). Every query and mutation takes that orgId
explicitly.
The roster: human and LLM agents in one table
TeleQuick keeps both kinds of worker in a single roster row so that reporting, hierarchy, and hotline assignment work identically for a person and for an AI agent. Thekind column picks one of two
field sets on the same table:
Switching kind in the wizard clears the fields that do not apply to the
new kind, so an LLM agent never carries a stale
staff_id and a human
never carries a stale default_model.
The header strip above the table shows the counters the list procedure
returns alongside the rows: total, active, humans, llms,
onboarding, on_leave, suspended. When the roster query is still
loading or has failed, the subtitle reads loading… / unavailable
rather than a count — a failed fetch must not look like an empty org.
LLM rows, the Humans / LLMs counter, and the Human/LLM kind chips are
hidden when the showLLM tweak is off. With the tweak off, the list
query is pinned to kind: 'human'.
Identity fields and the external agent ID
Two identity fields are required for every agent, both kinds:- Display name — what the table, avatar, and every report show.
- External agent ID — your own identifier for the agent (the dialer or workforce system’s agent id). It renders in monospace under the display name and is one of the fields the search box matches.
email as an email address if
present. LLM agents instead require an agent pool (see below). The
wizard hides these messages until your first Continue or Create
agent, then tracks each field live; if a required identity field is
still missing when you press Create agent, the wizard jumps back to
the Identity step.
Optional human fields on the Identity step: staff_id, position,
employee_type. employee_type is one of:
The Last login column is not backed by a server field yet and
renders
— for every row.
Status vocabulary and the Inactive flag
status is the workflow state of the agent. The Status filter also
offers Inactive, which is not a status — it is the soft-delete
flag.
The distinction that matters: Inactive is the row-level soft-delete
flag (
active = false), while Terminated is an employment status.
Selecting Inactive clears the status constraint and filters on the flag
instead; the two are never sent together.
The wizard can only create an agent as active or onboarding.
Assignments: site, team manager, team leader, hotline
Four nullable foreign keys place an agent in the org hierarchy. The directory resolves three of them to human-readable codes using lookup queries run in parallel on mount:
Any unresolved or unset key renders
—. All four are optional at
creation; you can leave the whole Hierarchy step untouched and fill it
in later from the agent’s detail page.
Site and hotline lookups expose both a name and a code: the name is
the label in pickers, the code is the hint and the column value.
Linking an LLM agent to an agent pool
Forkind = 'llm', agent_config_id is required. Rows in
agent_config are the agent pools — the AI voice-agent configs the
runtime binds to. The wizard loads the org’s pools
(agent_config.id, name, ordered by created_at) and presents them as
a picker, so you select a pool rather than paste a raw id.
Alongside the pool, an LLM agent can carry default_provider,
default_model, and hourly_cost_paise. These are optional and are
sent as null when blank.
Because the roster row points at the config, deleting an LLM agent also
unpublishes its config — see Deactivate vs Delete.
Skill assignments
The wizard’s Skills step picks fromadminSkills.list and attaches each
choice with two integers:
Skills are written after the agent row exists: the wizard creates
the agent, takes the returned
id, then fires one
adminAgents.upsertAgentSkill call per skill in parallel. Failures are
tolerated per row. If some succeed and some fail you get a warning toast
naming the counts, the agent is still created, and you retry the failed
ones from the agent’s Skills tab. upsertAgentSkill is an upsert, so
retrying is safe.
Filtering, search, and pagination
All filtering happens server-side. The list procedure accepts:
Changing any filter or the search text resets
page to 1. The response
carries rows, total, perPage, and counters; the pager derives the
page count from total / perPage.
The previous page’s data stays on screen while the next page loads
(keepPreviousData), so the table does not blank out between pages. The
lookup queries for sites, hotlines, and team leaders are cached and
shared by the filter chips and the FK columns. The header shows the
timestamp of the last successful fetch and a refresh button that
refetches the roster.
Bulk reassignment
Select rows with the checkbox column to reveal the bulk bar. Two of its actions are reassignments, both implemented withadminAgents.bulkUpdate over the selected ids:
Both prompt you to pick the new value first; cancelling the picker
cancels the mutation. On success the roster is invalidated and
refetched, the selection clears, and a toast reports the
updated
count returned by the procedure.
Deactivate vs Delete
These are not two flavours of the same thing. Choose based on whether you still need the agent’s history to be readable. Deactivate (adminAgents.deactivate) marks the selected agents
terminated with today’s end_date and accepts an optional free-text
reason (for example resigned, attrited, layoff). The rows stay in
dim_agent, so historical reporting joins still resolve the agent’s
name and attributes. The success toast reports the deactivated count.
This is the action to use when you only want to stop someone taking
calls.
Delete (adminAgents.delete) drops the rows permanently and cannot
be undone. Historical reporting that joins against those agents loses
their name and attributes. For LLM agents, deletion also unpublishes the
linked LLM configs — the response returns both deleted and
configsDeleted, and the toast reports the number of configs
unpublished.
Both actions confirm first, and Delete’s confirmation explicitly points
you at Deactivate as the reversible alternative.
CSV export columns
Two exports are available, both generated in the browser from the rows currently rendered:- Export CSV in the filter bar — the current page, written to
agents-page-<n>.csv. - Export selected in the bulk bar — the selected rows, written to
agents-selected.csv.
Seat caps and what happens at the limit
Roster size is bounded by the org’s entitlement claimlimits.max_concurrent_agents. The wizard reads the cap from the
entitlement hook and the current roster size from the same counters.total
the directory header uses.
When both numbers are known and the cap is a positive number, the wizard
treats the org as at capacity once the total reaches the cap. In that
state:
- Create agent is disabled and relabelled with the count and the cap.
- Hovering it explains that the limit is reached and that you need to upgrade the plan.
- Submitting anyway surfaces an inline error naming the count, the cap, and the upgrade path.
Related
- Authentication — org-scoped API keys used by the control-plane API
- Telephony Metrics — the reporting that agent rows join against