cd /news/artificial-intelligence/how-to-build-an-ai-employee-with-a-kโ€ฆ ยท home โ€บ topics โ€บ artificial-intelligence โ€บ article
[ARTICLE ยท art-113742] src=dev.to โ†— pub= topic=artificial-intelligence verified=true sentiment=ยท neutral

How to Build an AI Employee With a Knowledge Graph (Not Just Another Agent)

A developer detailed how to build an AI employee using a knowledge graph, contrasting it with typical AI agents. The approach, exemplified by the Roster project, uses a graph to store state, ownership, and history, enabling agents to work across extended periods by waking on events, reasoning, acting, and recording results. The post includes a minimal TypeScript implementation and a scheduling mechanism for future tasks.

read6 min views2 publishedAug 28, 2026

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

Roster:

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.

โ”€โ”€ more in #artificial-intelligence 4 stories ยท sorted by recency
github.com ยท ยท #artificial-intelligence
Open Session
โ”€โ”€ more on @roster 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain โ€” perfect for shipping the agent you just read about.

$git push zahid main
โ†’ Live at https://your-agent.zahid.host โœ“
Get free account โ†’ Pricing
from โ‚ฌ0/mo ยท no card required
LIVE [news/how-to-build-an-ai-eโ€ฆ] indexed:0 read:6min 2026-08-28 ยท โ€”