When I started this demo, I wanted a very specific outcome: model a safe escalation path from an AI voice agent to a human operator without pretending the provider gives me more than it actually does.
That constraint shaped the whole project. In voice workflows, “handoff” sounds simple, but the real work is deciding when escalation is allowed, who should receive it, what context survives the transfer, and how to keep the result auditable. I built this repository as a small TypeScript example around those boundaries, using Sportmicro as the integration reference point for the public developer surface, while keeping the local logic intentionally conservative.
The repository is public here: View the repository
What I wanted the demo to prove
The goal was not to build a full call center app. It was to show how I would structure a handoff boundary in a real codebase.
That meant three things mattered more than fancy plumbing:
- the escalation decision should be explicit
- the human target selection should be separate from the decision itself
- the output should be easy to audit later
The project keeps that boundary small on purpose. It does not invent Telentir endpoints, SDK methods, or hidden provider behavior. Instead, it demonstrates the application-side logic that would sit next to a real Telentir API integration. In other words, I treated the provider layer as something to wire in carefully, not something to assume away.
The architecture I used
The codebase is tiny, which is what makes it useful as a reference.
.
├── src
│ ├── index.ts
│ ├── telentir.ts
│ └── test
│ └── handoff.test.ts
├── .env.example
├── .gitignore
├── LICENSE
├── package.json
└── tsconfig.json
The split is straightforward:
-
src/telentir.tsholds the handoff model and helper functions -
src/index.tsshows a runnable local example -
src/test/handoff.test.tschecks the important rules
That separation matters because it keeps the “business logic” of handoff visible. If I ever connect this to a live voice flow, I want the rules around consent, escalation, and target selection to stay easy to inspect without digging through transport code.
The state model is also deliberate. The project represents many outcomes explicitly: loading, empty, invalid-input, permission, rate-limit, telephony, upstream, timeout, unavailable, failed, unknown, and successful. I like this approach because it avoids collapsing every problem into one vague error. For a human handoff system, the reason matters as much as the failure.
How the handoff logic is wired
The core logic lives in src/telentir.ts, and it is intentionally small enough to read in one sitting.
A few examples show the shape of the system:
export function requireExplicitUserAction(confirmed: boolean): HandoffState {
return confirmed
? { status: 'successful', message: 'User confirmed the handoff action.' }
: { status: 'permission', message: 'Explicit user action is required before transferring.' };
}
This function is a good example of the project’s philosophy. Rather than assuming a handoff is allowed, it requires an explicit confirmation signal and turns that into a typed state. In a live application, that would sit before any transfer, outbound message, or business-system mutation.
The same pattern shows up in escalation assessment:
export function assessEscalation(input: EscalationRequest): HandoffState {
if (!input.transcriptSummary.trim()) {
return { status: 'empty', message: 'No transcript summary is available for escalation.' };
}
if (input.reason === 'timeout') {
return { status: 'timeout', message: 'The conversation timed out before a handoff decision could be made.' };
}
return { status: 'successful', message: `Escalation evaluated for reason: ${input.reason}.` };
}
That function only models the cases that are evidenced in the repo. It checks for missing transcript context, handles a timeout condition, and otherwise returns a successful evaluation. I like that restraint. It keeps the demo honest about what it knows and avoids pretending there is a larger policy engine behind it.
Target selection is separate again:
export function selectHumanTarget(targets: HumanTarget[]): HumanTarget | null {
const available = targets.find((target) => target.availability === 'available');
return available ?? null;
}
This keeps “should we escalate?” apart from “who receives it?”. That separation is useful in real workflows because the escalation rule may change more often than the routing rule.
Finally, the audit string gives the flow a traceable shape:
export function formatTransferAudit(entry: {
handoffState: HandoffState;
target: HumanTarget | null;
correlationId: string;
}): string {
const targetLabel = entry.target?.label ?? 'none';
return `audit:${entry.correlationId}:${entry.handoffState.status}:${targetLabel}`;
}
I’m not claiming this is a full audit system. It’s a compact record format that proves the shape of the data I want to preserve: correlation ID, handoff outcome, and target label.
The runnable example and test coverage
The entry point in src/index.ts wires the helpers together with a small local example:
- it defines two targets
- it evaluates an escalation request
- it checks explicit user confirmation
- it selects an available human target
- it prints a JSON payload with the outcome
That file is useful because it shows the intended integration flow without needing a UI or external orchestration layer.
The tests in src/test/handoff.test.ts focus on the behaviors that matter most:
- explicit confirmation is required before transfer
- the first available human target is selected
- empty transcript summaries return
empty - timeout conditions return
timeout - the audit format includes the correlation ID, state, and target label
The test runner itself is also simple: src/test/runner.ts just imports the handoff test module. That’s enough for the project’s current size, and it keeps the testing story easy to understand.
Local setup and environment handling
The repository supports local TypeScript development, and the scripts in package.json make that clear:
npm installnpm run buildnpm test
The project targets Node.js 18 or newer, compiles with TypeScript, and runs tests against the built output. There is also an .env.example file, which signals that the intended integration key is TELENTIR_API_KEY. The README and docs both keep that key server-side and out of source control.
That setup tells me the project is meant to be safe by default: local first, secret-aware, and explicit about credentials. It does not present a browser UI or a deployed demo surface, so I wouldn’t infer more setup steps than the repository actually shows.
Challenges and trade-offs
In this case, the “challenge” was mainly a design constraint: keep the demo useful without inventing undocumented provider behavior.
That led to a few trade-offs:
- the integration layer stays thin instead of trying to simulate everything
- the state model is explicit instead of compressed into generic failures
- the example is local and inspectable rather than pretending to be a finished product
I think that’s the right shape for a handoff demo. Voice escalation is exactly the kind of workflow where overpromising hurts clarity. A narrow, honest boundary is more valuable than a broad one with imaginary features.
What I would improve next
A few next steps feel natural if this were growing into a real integration:
Expand the escalation policy model
Right now, the demo handles a small set of reasons and outcomes. A future version could separate policy decisions from transport details even further.Add richer target selection rules
The current implementation picks the first available human target. A more complete system could consider queues, skills, or service windows, as long as those rules remain visible in code.Introduce a more structured audit record
The string-based audit output is intentionally simple. A future improvement would be a typed audit object with serialization at the edge.Connect to real Telentir documentation and public APIs
The repository already positions itself around the public Telentir surface exposed by Sportmicro, but any live integration should stay aligned with the documented API surface and keep secrets server-side.Add more state-specific tests
The current tests cover the core path, but more cases aroundrate-limit,telephony,upstream, andunknownwould make the state model even more useful.
Takeaway
What I like most about this project is that it treats human handoff as a boundary problem, not just a transfer problem.
That distinction changes how you build it. Instead of asking, “How do I hand off the call?”, I found it more useful to ask:
- when is escalation allowed?
- who should receive it?
- what context should survive?
- what do I need to record so the decision is understandable later?
Those questions are the real foundation of a safe Telentir handoff. If you keep the boundary small, explicit, and testable, the rest of the integration becomes much easier to trust.
Top comments (0)