From experiment to live product: how we built Yada, added a TypeScript agent runtime to a Go platform, brought conversational AI to TalonHR, and released the harness as open source.
Over the first three parts of this series, I documented our work building an AI harness around TalonHR. We began by exploring how an agent could interact with an existing application, then worked through tool orchestration, execution loops, permissions, state management, and the rest of the engineering needed to make such a system reliable in production.
Now we have something people can actually use.
Meet Yada, our open-source AI agent harness, now live inside TalonHR. It's a standalone TypeScript project for bringing conversational, tool-using agents into existing applications. The source code is on GitHub under the MIT license, and the project has its own home at yada.edgar.co.ke.
TalonHR is its first production deployment.
How we got here
For about a month, we deliberately slowed feature development on TalonHR. Before building more, we wanted to understand how our early adopters were actually using the platform: how they registered, how they applied for roles, how they navigated, and where they got stuck.
Some findings were funny. Hundreds of users replied to our verification emails with "Yes" or "It's me" instead of clicking the link. Rather than fight it, we added rules that accept a valid reply from the registered address within the verification token's lifetime.
Other findings were less amusing. Workflows we had assumed were straightforward turned out to carry unnecessary complexity. We used those lessons to simplify the platform and improve adoption, then resumed shipping features in phases.
Yada is the first major feature to come out of that process. It also opens our next round of validated learning, this time focused on how people interact with an application through natural language.
What is Yada?
Yada is an agent harness for Node.js applications. It handles the infrastructure needed to run a tool-using agent: model calls, tool discovery and execution, permissions, approvals, state management, and recovery. Your application supplies the business logic, authentication, and integrations; Yada connects those capabilities to a language model and manages execution.
It works with Anthropic, OpenAI, Gemini, DeepSeek, Ollama, llama.cpp, and any OpenAI-compatible endpoint. Tools can come from typed functions, MCP servers, HTTP APIs, databases, or reusable Agent Skills. The project also ships a service API, a browser client, and an embeddable chat widget.
We built it to be useful beyond TalonHR: something a developer can add to an existing application without redesigning the backend.
Why TypeScript when TalonHR runs on Go?
This is one of the more interesting architectural decisions we made.
TalonHR's backend is written in Go, which powers our services, APIs, business logic, and workflows. The web frontend uses Next.js, and the mobile apps are built with Flutter. Adding an agent runtime in TypeScript means adding yet another language and runtime to a system that already has plenty of moving parts.
We did it deliberately.
Go stays responsible for TalonHR's business logic. We didn't want to duplicate those capabilities inside an agent, or migrate working systems just because we were introducing AI. Instead, Yada takes on the agent-specific work: model communication, tool orchestration, execution state, approvals, and conversation management. TypeScript also gives us a mature ecosystem of model SDKs, MCP tooling, schema validation, and browser integrations.
The decision that really matters is the boundary between the agent runtime and the application it serves. Yada doesn't need to know TalonHR's database models or recruitment workflows. It needs well-defined capabilities exposed by the application, backed by authenticated identities and enforceable permissions.
That separation lets us develop and deploy the agent independently while the Go services keep ownership of their business rules. It also means another team could pair Yada with a Python, Java, PHP, or Go backend through a suitable service or tool interface.
How the components fit together
TalonHR Users
|
+---------+---------+
| |
Next.js Web Flutter Mobile
| |
+---------+---------+
|
Authenticated API
|
+--------+--------+
| |
Go Services Yada Service
| TypeScript
| |
| Agent Runtime
| |
| Model Providers
| |
+------ Tools ----+
|
Business Operations
|
Data Storage
This shows the logical integration boundaries, not a deployment layout. The key point is that Yada doesn't replace the application. Our Go services still enforce business rules, validate requests, and manage data; the agent works only through the capabilities made available to it.
That also makes the system easier to maintain. We can improve Yada's execution engine without touching recruitment logic, and we can change application workflows without rebuilding the agent.
Building the agent runtime
Yada runs a bounded execution loop. When a user sends a request, the runtime works out which tools that authenticated user may access, then passes the relevant context and tool definitions to the configured model provider.
The model either responds or requests one or more tool calls. Yada validates the arguments, checks authorization, runs the permitted tools, and returns the results to the model. The loop continues until the model produces a final answer, an action needs approval, or execution hits a configured limit.
Those limits cover model rounds, tool calls, consecutive failures, parallel execution, and output size. Read operations can run concurrently; mutations follow a more controlled path. That matters in an application like TalonHR, where users hold different permissions and some actions affect real people.
Actions, permissions, and approvals
An agent that reads information is relatively easy to reason about. An agent that changes information carries far more responsibility.
Suppose the agent is about to update a candidate record or advance a step in a recruitment workflow. Even if the model has understood the request perfectly, we still need to know whether the user is authorized, exactly what will change, and whether the action has already happened.
Yada handles mutations in four stages:
- Preparation. Before anything runs, the system can produce a preview of the proposed change.
- Approval. An approval is bound to the authenticated user, the specific operation and its arguments, the relevant state version, and an expiry time.
- Execution. The application enforces authorization and state checks before applying the change. Every operation carries an identifier the backend can use to prevent duplicate execution.
- Reconciliation. If the response is lost after an operation starts, Yada reconciles the outcome against an authoritative receipt instead of blindly retrying.
That last stage matters whenever an agent touches production systems. A retry that looks harmless in conversation can create duplicate records or repeat an irreversible action.
Making execution durable
We also spent considerable time on recovery. An agent may be several steps into a task when a worker crashes, a deployment interrupts it, or the client disconnects. We wanted those events to be recoverable without restarting every task from scratch.
Yada supports durable execution through persisted checkpoints, storing conversations, execution state, approvals, and recovery data in SQLite, PostgreSQL, or MySQL. Workers coordinate through renewable leases, so competing workers can't overwrite each other's progress. During recovery, completed tool results can be reused, while pending operations are rechecked against current permissions. For uncertain writes, the runtime reconciles rather than replaying the mutation.
There are limits. A model request interrupted before its response was checkpointed may have to be repeated, and external operations still need proper idempotency support from the systems that own them. We've documented these boundaries because anyone building on the project needs to know them.
Yada on mobile
Keeping the agent behind a service interface means its capabilities aren't tied to any one frontend.
TalonHR's Flutter apps for Android and iOS serve applicants, employers, recruiters, and coaches, and we want users to reach the same capabilities whether they're at a desk or on a phone. For an agent, that means keeping model execution, tool access, authentication, and approval logic on the server. The mobile app simply becomes another client.
Yada's service provides durable conversations, run submission, execution events, cancellation, and approval decisions. The open-source project includes a browser client and chat widget; a Flutter app integrates with the same service API through its own client implementation.
The API also lets clients reconnect to a run and retrieve persisted execution events. That's especially useful on mobile, where connections drop as people switch apps or move between networks. The server keeps processing a submitted run even if the client disconnects.
The result is a foundation for conversational workflows across devices, without duplicating the agent runtime inside each app.
TalonHR for Android and iOS
TalonHR is available on both major mobile platforms:
- Android: Download TalonHR on Google Play
- iPhone and iPad: Download TalonHR on the App Store
We recently passed our first 100 combined installs. It's early, but mobile gives us another view into how people prefer to use the platform. We're particularly curious how conversational workflows fit on mobile, where screen space is tight and navigating complex interfaces takes more effort.
What Yada is doing for TalonHR
TalonHR brings applicants, employers, recruiters, and coaches onto one platform, and as the product has grown, so has the number of workflows. Applicants find opportunities, manage profiles, submit applications, and track their progress. Employers and recruiters manage roles, candidates, interviews, and hiring activity.
Yada adds a conversational way into supported functionality. Instead of clicking through several pages to find an operation, a user can describe what they want to do, and the agent uses the tools available to that user to help get it done. What it can do depends on the user's permissions and the tools we've exposed.
We're rolling these capabilities out in phases. We want to learn which interactions people find useful, where they need clarification, and which workflows are better left to the traditional interface.
It's tempting to expose every application function to an agent on day one. We've resisted. We'd rather make a small set of workflows reliable and understandable before expanding what the agent is responsible for.
Evaluating the agent
Yada includes an evaluation framework for defining repeatable scenarios with expected outcomes, tool calls, and execution behaviour. A deterministic fake provider lets us test the runtime without calling external models or spending tokens, and the same scenarios can run against live providers:
npm run evals -- --provider anthropic
Or target a single scenario:
npm run evals -- --provider openai --scenario "skill read"
The runner reports pass/fail results and token usage, which helps us catch regressions in tool execution, approval handling, and other runtime behaviour.
But evaluations only cover the situations we've anticipated. The next lessons will come from real users: which requests succeed, which fail, where people abandon conversations, and how often the agent has to ask for more information. Those observations will shape what we improve next.
Why we open-sourced Yada
We started out needing an agent for TalonHR. But the problems we ran into weren't unique to recruitment software. Tool orchestration, permissions, durable execution, approvals, provider integrations, and recovery come up for anyone connecting agents to an existing application.
We had already invested the time to solve them. Separating that work from TalonHR made the runtime reusable, and publishing it lets other developers inspect the implementation and build on it.
The MIT-licensed repository includes the runtime, model adapters, storage implementations, MCP integrations, service API, browser components, tests, examples, and documentation. You can read the code, report issues, propose improvements, or contribute integrations. We'd especially value feedback from developers building agents for applications with real users and real business operations.
Getting started
Install Yada into an existing TypeScript application:
npm install @edgaralochi/yada
Or generate a starter app:
npx @edgaralochi/yada init my-agent
cd my-agent
The starter includes storage, typed tools, skills, and a chat widget. Add your model provider credentials to .env.starter, then run:
npm start
Yada targets Node.js 24 and exposes separate package entry points for providers, storage, MCP, skills, the service layer, the browser client, and the widget. The documentation covers configuration, deployment, recovery, authorization, and testing.
Website: https://yada.edgar.co.ke Source code: https://github.com/edgar4/yada
Where we go from here
Both Yada and TalonHR are at an interesting stage. The agent is live, the runtime is open source, and we're starting to learn how people use it.
What we build next will follow from those observations: better conversation quality, more useful integrations, stronger evaluations, and a runtime that's easier for other developers to adopt. We're also curious to see what happens when conversational interaction is available across every device and user role.
There are plenty of engineering problems left to solve. For now, though, I'm pleased we've reached this point. This series began as an experiment with an AI harness around an existing product. It has grown into a standalone open-source project, with TalonHR as its first production application.
If you're building an agent-powered product, take a look at Yada or explore the source code on GitHub. And to see the platform where it all started, visit TalonHR or download the app for Android or iOS.
That's Part 4. I suspect production will hand us plenty of material for Part 5.