An AI agent can take an action. An AI employee needs to know what happens next.
Most AI agents look something like this:
Think โ Act โ Observe โ Repeat
That's fine for short-lived tasks.
But an AI employee needs to work across hours, days, and weeks.
It needs to remember:
That's where graph engineering becomes interesting.
This is the architecture behind
software that can own work the way an employee does, not just fire off a single tool call.
Events wake someone up. A graph holds state, ownership, and history.
The agent reasons, acts, writes the result back, then sleeps until the next event.
For Roster, the loop looks like this:
Event
โ
Graph
โ
Agent
โ
Action
โ
Graph Update
โ
Sleep
โ
Wake Again
Let's build a tiny version.
Imagine an AI employee called Maya.
Her job is simple:
Follow up with sales leads.
Her world contains:
Maya
โ owns
Lead
โ belongs_to
Company
โ contacted
Email
โ replied_to
Customer
We don't need a massive graph database.
We just need nodes and relationships.
Here's a minimal TypeScript graph:
type Node = {
id: string;
type: string;
data: Record<string, unknown>;
};
type Edge = {
from: string;
to: string;
type: string;
};
class Graph {
nodes = new Map<string, Node>();
edges: Edge[] = [];
addNode(node: Node) {
this.nodes.set(node.id, node);
}
connect(from: string, type: string, to: string) {
this.edges.push({ from, type, to });
}
neighbors(id: string) {
return this.edges
.filter((edge) => edge.from === id)
.map((edge) => ({
relationship: edge.type,
node: this.nodes.get(edge.to),
}));
}
}
Now create Maya and a lead:
const graph = new Graph();
graph.addNode({
id: "maya",
type: "employee",
data: {
name: "Maya",
role: "sales",
},
});
graph.addNode({
id: "lead-123",
type: "lead",
data: {
company: "Acme",
status: "qualified",
},
});
graph.connect("maya", "owns", "lead-123");
Our graph now knows:
Maya โโownsโโโ Lead #123
That's already more useful than two disconnected database records.
Employees shouldn't constantly run.
Something should wake them up.
For example, Sarah replies to an email:
const event = {
id: "event-1",
type: "email.replied",
data: {
leadId: "lead-123",
message: "Sounds interesting. Follow up next Tuesday.",
},
};
Now we can find the employee responsible for that lead:
function findOwner(event: typeof event) {
const leadId = event.data.leadId;
return graph.edges.find(
(edge) => edge.to === leadId && edge.type === "owns"
)?.from;
}
const employeeId = findOwner(event);
console.log(employeeId);
// maya
We just answered:
Who should wake up?
The flow becomes:
Email Reply
โ
Event
โ
Find Lead
โ
Find Owner
โ
Wake Maya
Now we give Maya an actual agent loop.
async function runEmployee(employeeId: string, event: any) {
const employee = graph.nodes.get(employeeId);
const context = {
employee,
event,
relationships: graph.neighbors(employeeId),
};
const decision = await agent(context);
const result = await execute(decision);
recordResult(employeeId, decision, result);
}
The important part is the sequence:
Wake
โ
Read Graph
โ
Reason
โ
Act
โ
Record
The graph gives the agent persistent context.
Now imagine Sarah says:
Follow up with me next Tuesday.
Maya shouldn't stay running until Tuesday.
She schedules a future event.
type Job = {
employeeId: string;
runAt: Date;
event: any;
};
const jobs: Job[] = [];
function schedule(job: Job) {
jobs.push(job);
}
Maya can schedule her next action:
schedule({
employeeId: "maya",
runAt: new Date("2026-09-01T09:00:00Z"),
event: {
id: "followup-1",
type: "followup.due",
data: {
leadId: "lead-123",
},
},
});
Then Maya sleeps.
When the time arrives:
async function processJobs() {
const now = new Date();
for (const job of jobs) {
if (job.runAt <= now) {
await runEmployee(job.employeeId, job.event);
}
}
}
Now we have two ways to wake an employee:
Customer Reply โโโโโโโ
โ
Approval Granted โโโโโผโโโ Wake Employee
โ
Schedule Due โโโโโโโโโ
Now let's connect an LLM.
The agent gets the relevant graph context and decides what to do.
async function agent(context: any) {
const prompt = `
You are Maya, a sales employee.
Your job is to follow up with leads.
Event:
${JSON.stringify(context.event)}
Graph:
${JSON.stringify(context.relationships)}
Decide the next action.
Return JSON:
{
"action": "...",
"reason": "...",
"runAt": "..."
}
`;
return llm.generateObject(prompt);
}
For Sarah's message, Maya might return:
{
"action": "schedule_followup",
"reason": "Sarah requested a follow-up next Tuesday.",
"runAt": "2026-09-01T09:00:00Z"
}
Then we execute it:
async function execute(decision: any) {
switch (decision.action) {
case "schedule_followup":
schedule({
employeeId: "maya",
runAt: new Date(decision.runAt),
event: {
id: crypto.randomUUID(),
type: "followup.due",
data: decision,
},
});
return {
success: true,
};
case "send_email":
return sendEmail(decision);
default:
throw new Error(`Unknown action: ${decision.action}`);
}
}
Finally, record what happened:
function recordResult(employeeId: string, decision: any, result: any) {
graph.addNode({
id: crypto.randomUUID(),
type: "agent_action",
data: {
employeeId,
decision,
result,
createdAt: new Date(),
},
});
}
Now the employee has memory.
Not necessarily memory as a giant conversation transcript.
Memory as state and relationships.
Let's walk through the entire workflow.
Sarah replies:
Sounds interesting. Follow up next Tuesday.
Gmail
โ
email.replied
Email
โ
related_to
โ
Lead #123
Lead #123
โ
owned_by
โ
Maya
Maya
โ
Read Graph
โ
Understand Context
Sarah wants a follow-up next Tuesday.
Task
โ
scheduled_for
โ
Tuesday 9:00 AM
Maya sleeps ๐ค
Tuesday arrives
Scheduler
โ
followup.due
โ
Wake Maya
Maya
โ
Lead #123
โ
Sarah
โ
Previous Conversation
Maya
โ
send_email()
โ
Sarah
Task #123
status = completed
Email #43
status = sent
Lead #123
last_contacted = today
Then:
Maya
โ
Sleep
That's a tiny AI employee.
The architecture is surprisingly simple:
โโโโโโโโโโโโโโโ
โ Events โ
โโโโโโโโฌโโโโโโโ
โ
โโโโโโโโโโโโโโโ
โ Graph โ
โ โ
โ State โ
โ Relations โ
โ History โ
โโโโโโโโฌโโโโโโโ
โ
โโโโโโโโโโโโโโโ
โ AI Employee โ
โโโโโโโโฌโโโโโโโ
โ
โโโโโโโโโดโโโโโโโโ
โ โ
Tools Scheduler
โ โ
โโโโโโโโโฌโโโโโโโโ
โ
Events
โ
โโโโโโ Graph
The important part is the final arrow:
Agent
โ
Action
โ
Event
โ
Graph
โ
Next Decision
The agent changes the world.
The graph records the change.
The next time the employee wakes up, it doesn't start over.
It continues.
I think the future of AI employees looks less like:
Prompt โ LLM โ Tool
and more like:
World
โ
Graph
โ
Agent
โ
Action
โ
Event
โ
Graph
The LLM provides reasoning.
The tools provide capabilities.
The scheduler provides time.
Events provide wake-ups.
The graph provides continuity.
That's the interesting part.
We're not just building agents that can do things.
We're building software that can own work.
That's what Roster is for.
If the same follow-ups, handoffs, and waiting loops keep eating your week, give them to an AI employee.