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

> Source: <https://dev.to/bobbyhalljr/how-to-build-an-ai-employee-with-a-knowledge-graph-not-just-another-agent-1098>
> Published: 2026-08-28 03:12:27+00:00

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](https://get-roster.com):

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:

``` js
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:

``` js
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:

``` js
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.

``` js
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.

``` js
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:

``` js
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.

``` js
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](https://get-roster.com) is for.

If the same follow-ups, handoffs, and waiting loops keep eating your week, give them to an AI employee.
