Phase 6 of the roadmap: Team/Agent/AgentSkill/AgentAvailability models, dynamic HierarchyNode configuration, and a capability-eligibility read path for the future orchestration phase to call. Supersedes the pre-existing identity/agents scaffold stub (which queried a generic User/UserRole model unrelated to this system's real architecture); leaves identity/customers and identity/auth untouched as out of scope. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
18 KiB
Feature Specification: Support Organization
Feature Branch: 006-support-organization
Created: 2026-09-02
Status: Draft
Input: User description: "Phase 6 of docs/10-implementation-roadmap.md: Team/Agent/ AgentSkill/AgentAvailability models, dynamic HierarchyNode configuration, admin hierarchy editor. Per docs/05-orchestration-sla-escalation.md §2-3 and docs/06-database-schema.md 'Domain: Support Hierarchy / Teams / Agents'."
User Scenarios & Testing (mandatory)
User Story 1 - An admin builds out teams and agents (Priority: P1)
An administrator creates a support team, then creates agents and assigns each to a team. An agent can be deactivated (e.g., they leave) without losing their history, and reactivated later.
Why this priority: Every later capability in this feature — skills, availability, hierarchy nodes that reference a team — and every later phase (orchestration, assignment) needs teams and agents to already exist. There is nothing to configure or query without this.
Independent Test: Create a team; create an agent assigned to it; confirm the agent appears under that team; deactivate the agent; confirm it's excluded from an active-agents listing but still resolvable by id.
Acceptance Scenarios:
- Given an admin creates a team, When it's saved, Then it exists with
active: trueand no agents yet. - Given an existing team, When an admin creates an agent assigned to it, Then the agent is retrievable both directly and as part of that team's roster.
- Given an active agent, When an admin deactivates them, Then they're excluded from any "active agents" listing but their record (and history — assignments, skills) is not deleted.
- Given a deactivated agent, When an admin reactivates them, Then they reappear in active listings.
- Given an admin deactivates a team, When it's saved, Then the team's own active agents are unaffected — team deactivation does not cascade into deactivating its agents.
User Story 2 - An admin manages agent skills and availability (Priority: P1)
An administrator tags an agent with capability skills (each with a proficiency level), and an agent's current availability — status, working hours, and current workload — is tracked as a single, current, updatable record per agent.
Why this priority: Skills and availability are what make an agent selectable at all — a future orchestration/assignment phase (Phase 7) is fundamentally "which agents have this skill and are currently available," so this data must be real and current before that phase, or any capability-matching read path in this one (User Story 4), means anything.
Independent Test: Tag an agent with a skill and a proficiency level; confirm it's listed on
the agent. Set the agent's availability status to busy; confirm a query for that agent reflects
it immediately. Update the same agent's availability again; confirm the prior value is replaced,
not duplicated.
Acceptance Scenarios:
- Given an agent, When an admin adds a skill tag with a proficiency level, Then the skill appears on the agent's record, and an agent can hold more than one skill tag at once.
- Given an agent already has a skill tag, When an admin updates its proficiency level, Then the level changes without creating a duplicate skill entry for the same tag.
- Given an agent with no availability record yet, When an admin sets one, Then a single current record exists with a status, working hours, and a current-load counter starting at zero.
- Given an agent's existing availability record, When its status is updated, Then the change is immediately reflected — never a second, competing record for the same agent.
- Given an availability status, When it's set, Then it MUST be one of a fixed,
validated set of values (
available,busy,away,offline) — an arbitrary string is rejected.
User Story 3 - An admin configures the dynamic support hierarchy (Priority: P2)
An administrator builds an arbitrary tree of support hierarchy nodes for routing — each node scoped to some combination of product/category/priority, tagged with the skills it covers, pointing at a team, and carrying the assignment-strategy/SLA-policy/escalation-policy references a future orchestration phase will read. Nodes can be nested under a parent, ordered relative to their siblings, and deactivated without being deleted.
Why this priority: This is the product's explicit differentiator (doc 01: "the customer never needs to understand the internal support hierarchy... changing one must never require a code change") — but it's meaningful only once teams exist (User Story 1), so it can't be P1 itself.
Independent Test: Create a root hierarchy node and a child node under it; confirm the child
resolves its parent correctly and both are ordered as authored. Deactivate a node; confirm it's
excluded from an active-tree listing but still exists. Attempt to create a node whose parentId
doesn't exist; confirm it's rejected.
Acceptance Scenarios:
- Given an admin creates a hierarchy node with no
parentId, When it's saved, Then it exists as a root node. - Given an existing node, When an admin creates another node with
parentIdset to it, Then the new node is retrievable as a child of the first, and the tree can be read back in full from any root. - Given an admin supplies a
parentIdthat doesn't correspond to any existing node, When the node is created, Then the request is rejected — a hierarchy can never reference a parent that doesn't exist. - Given a node, When an admin sets its
productScope/categoryScope/priorityScope/skills/assignmentStrategy/slaPolicyId/escalationPolicyId/entryConditions/exitConditions, Then every value is stored and returned exactly as given — none of it is interpreted or enforced by this feature itself (that's the future orchestration engine's job). - Given an active node, When an admin deactivates it, Then it's excluded from a query for the active tree but still exists and its children are unaffected (a deactivated parent does not implicitly deactivate its children).
- Given two sibling nodes under the same parent, When an admin sets their
ordervalues, Then reading the parent's children back returns them in that exact order.
User Story 4 - A capability-eligibility lookup for a future caller to use (Priority: P3)
Given a required skill (or set of skills) and optionally a product/category/priority context, a lookup returns the active agents who are eligible on capability grounds alone — team membership, skill match, active state. It deliberately does not consider availability, working hours, current load, or perform any assignment decision.
Why this priority: This is the read contract the future orchestration/assignment phase
(Phase 7) will call — analogous to how 004-product-knowledge built /knowledge/retrieve for the
future AI-support feature to call rather than that feature reaching into 004's internals. It
depends on User Stories 1-2 (agents/skills/teams must exist to be queried) and, for
product/category/priority-scoped lookups, on User Story 3's hierarchy nodes.
Independent Test: Tag two agents with different skills; query eligibility for one skill; confirm only the matching agent is returned. Deactivate that agent; confirm the query now returns nothing. Query with a skill no agent has; confirm an empty result, never an error.
Acceptance Scenarios:
- Given agents with different skill tags, When a capability lookup is run for a specific skill, Then only agents holding that skill are returned.
- Given doc 05 §3's explicit ordering ("capability is evaluated before availability"),
When the lookup runs, Then its result is never filtered or reordered by availability,
working hours, or current load — a
busyorofflineagent who has the skill is still included; that filtering is explicitly out of scope for this lookup. - Given an inactive agent who otherwise matches, When the lookup runs, Then they are excluded — capability matching never surfaces a deactivated agent.
- Given a skill no active agent currently holds, When the lookup runs, Then it returns an empty result, never an error.
- Given a lookup scoped to a product/category/priority, When hierarchy nodes exist whose
scope matches, Then the matching node(s)' own
skillsare included as additional eligibility criteria — the lookup composes hierarchy scoping with direct skill matching rather than requiring the caller to pre-resolve which node applies.
Edge Cases
- What happens if an admin tries to delete a team or agent that has assignment/ticket history (once that history exists in a later phase)? Out of scope for strict enforcement here — this feature does not implement hard deletion of teams, agents, or hierarchy nodes at all, only active/inactive state, consistent with this system's broader convention of never silently losing support-domain history.
- What happens if a hierarchy node's
parentIdis later changed to create a cycle (a node becomes its own ancestor)? Rejected — a hierarchy MUST remain a tree, never a cycle. - What happens when an agent is reassigned from one team to another? Supported as a normal update
to the agent's
teamId— the agent's skills and availability record are unaffected by a team change. - What happens if two admins race to set the same agent's availability at the same moment? The later write wins (last-write-wins) — availability is frequently-changing operational state, not the kind of durable business record this system's optimistic-concurrency convention (ticket status, knowledge versioning) is built for; see Assumptions.
- What happens when a capability lookup (User Story 4) is scoped to a product/category/priority that no hierarchy node covers? It falls back to a plain skill-only match across all active agents — an unconfigured scope is "match on skill alone," not "match nothing."
Requirements (mandatory)
Functional Requirements
- FR-001: The system MUST let an admin create a team, defaulting to
active: true. - FR-002: The system MUST let an admin create an agent assigned to a team, defaulting to
active: true. - FR-003: Deactivating an agent MUST exclude them from any active-agent listing without deleting their record or any of their skill/availability data.
- FR-004: Deactivating a team MUST NOT cascade into deactivating its agents.
- FR-005: The system MUST let an admin add a skill tag with a proficiency level to an agent, and MUST support an agent holding multiple distinct skill tags at once.
- FR-006: Updating an existing skill tag's proficiency level for an agent MUST replace the level, never create a duplicate entry for the same agent+tag pair.
- FR-007: The system MUST let an admin set an agent's availability (status, working hours, current load), maintaining exactly one current record per agent — never more than one.
- FR-008: An availability
statusMUST be validated against a fixed set of values (available,busy,away,offline); any other value MUST be rejected. - FR-009: The system MUST let an admin create a hierarchy node, optionally under a parent
node; creating a node with a
parentIdthat does not reference an existing node MUST be rejected. - FR-010: A hierarchy node MUST store
name,order, an optionalteamId,skills,productScope,categoryScope,priorityScope,assignmentStrategy, optionalslaPolicyId/escalationPolicyId, and optionalentryConditions/exitConditions, returning every value exactly as given — this feature does not interpret, execute, or validate the semantics ofassignmentStrategy/entryConditions/exitConditionsbeyond storing them. - FR-011: The system MUST reject a hierarchy node update that would introduce a cycle (a node set as its own ancestor, directly or transitively).
- FR-012: Deactivating a hierarchy node MUST exclude it from an active-tree query without deleting it or affecting its children's own active state.
- FR-013: Sibling hierarchy nodes under the same parent MUST be returned in their authored
orderwhen the parent's children are read. - FR-014: The system MUST provide a capability-eligibility lookup that, given a required
skill (or skills) and an optional product/category/priority context, returns active agents
whose team is active and who hold a matching skill — composing a matching hierarchy node's own
skillsinto the criteria when the context resolves to one. - FR-015: The capability-eligibility lookup MUST NOT filter or reorder its result by availability status, working hours, or current load (doc 05 §3's explicit capability-before- availability ordering) — that consideration belongs to the future orchestration/assignment phase, not this lookup.
- FR-016: The capability-eligibility lookup MUST exclude inactive agents and MUST return an empty result, never an error, when nothing matches.
- FR-017: Every hierarchy node creation, edit, and active-state change MUST be written to the durable audit log (Constitution Principle VI; doc 07 explicitly lists "hierarchy changes" as an audited action).
Key Entities
- Team: A named grouping of agents, with an active/inactive state independent of its members' own active states.
- Agent: A support staff member belonging to a team, with an active/inactive state, a set of skill tags, and at most one current availability record.
- Agent Skill: A capability tag held by an agent, with a proficiency level used by future skill-based assignment logic — not enforced or interpreted by this feature.
- Agent Availability: An agent's current operational state — status, working hours, and current workload — exactly one record per agent, always representing the latest known value.
- Hierarchy Node: A configurable, nestable support-routing unit — scoped to product/category/ priority, tagged with skills, pointing at a team and at (not-yet-built) SLA/escalation policy references, with entry/exit conditions and an assignment-strategy reference — all of it stored as data for a future orchestration engine to interpret, never hardcoded logic in this feature.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: 100% of deactivated agents are absent from every active-agent listing, while remaining individually retrievable by id.
- SC-002: An agent's availability is always represented by exactly one record — never zero after being set once, never more than one after any number of updates.
- SC-003: 100% of hierarchy node creation attempts with a nonexistent
parentIdare rejected, never silently creating an orphaned or root node instead. - SC-004: 100% of hierarchy trees remain acyclic — no update can produce a node that is its own ancestor.
- SC-005: A capability-eligibility lookup for a skill no active agent holds returns an empty result in 100% of cases, never an error.
- SC-006: Every hierarchy node change (create, edit, activate/deactivate) produces exactly one corresponding audit log entry.
Assumptions
- Availability updates use last-write-wins, not optimistic concurrency — unlike ticket status
or knowledge versioning (this system's durable-record convention), an agent's availability is
frequently-changing operational telemetry, not a business record whose history matters; adding
expectedVersionhere would add friction without protecting anything meaningful. If a future phase finds contention here a real problem (e.g., automated availability updates racing an agent's own manual one), that's a decision for that phase to revisit, not this one. - This feature does not build the orchestration engine, assignment strategies, or SLA/
escalation policy models —
assignmentStrategy,slaPolicyId, andescalationPolicyIdon a hierarchy node are stored exactly as given (free-text/reference values) with no validation against a real policy table, because those tables don't exist until Phase 7/8. This mirrors how 004-product-knowledge storederrorCodeas free text pending 004's own laterErrorCodemodel in the same feature — here, the referenced tables are a genuinely later phase, not later in the same feature, so the reference fields stay untyped/unvalidated for now. - The capability-eligibility lookup (User Story 4) is a read contract for a future caller, not
the orchestration engine itself — it answers "who is capability-eligible," never "who should
be assigned." Availability, workload, and strategy selection are explicitly out of scope, per
doc 05 §3's own ordering (capability before availability) and doc 05 §8 (orchestration needs
its own
engine//rules//strategies/internal structure — not appropriate to build speculatively inside this CRUD-shaped feature). identity/authremains a stub, unchanged by this feature — every admin endpoint here is gated by the samefastify.authenticateknown-limitation stub as every prior feature's admin surface; this feature does not attempt real agent authentication or a login flow (that's Phase 10's Agent/Admin UI territory, or wheneveridentity/authitself becomes its own feature).- The pre-existing
identity/customers/identity/agentsscaffold stubs (querying the genericUser/UserRolemodel from the original project scaffold) are superseded, not extended — a realAgentis its own first-class entity per doc 06, unrelated to the genericUsermodel (which nothing in this system's real architecture, starting from 002-saas-integration'sCustomerReference, actually uses).identity/customersand itsUser-based stub are left untouched (out of scope — this feature is about agents/teams/hierarchy, not customers);identity/agents' stub is replaced with a real implementation as part of this feature.