Chapter 4 ยท CORE

Central Data & Identity Server

๐Ÿ“„ 04_central_data___identity_server.md ๐Ÿท Core

Chapter 4: Central Data & Identity Server

Welcome to the fourth chapter of our journey into building airi.

So far, we have built:

  1. A Body: The Agent Adapters that let the AI interact with platforms like Discord or Minecraft.
  2. A Mind: The Cognitive Brain that allows the AI to think and plan.
  3. A Face: The "Stage" (Visual Presentation Layer) that gives the AI a visual presence.

But we have a problem. Right now, if you talk to your AI on your desktop computer, close it, and then open it on your phone... the AI on your phone has no idea who you are. It has amnesia.

In this chapter, we will build the Central Data & Identity Server. This is the long-term memory and the "Passport Office" of the ecosystem.

The Motivation: One Soul, Many Bodies

Let's look at our central use case: Continuity.

Imagine you are playing a game with your AI character on your PC. You unlock a special ability: "Fireball." Later, you go out for a walk and open the mobile app. You want that same character, with the same "Fireball" ability, to be there.

Without a central server:

With a central server:

  1. PC AI tells the Server: "User unlocked Fireball."
  2. Server writes this in the permanent record.
  3. Mobile AI asks the Server: "What abilities does this user have?"
  4. Server replies: "Fireball."

This server acts as the Single Source of Truth for everything: chat history, user accounts, and character personalities.

Key Concepts

To understand the server, think of a Public Library.

1. The Filing Cabinet (The Database)

This is where the actual information lives. We use PostgreSQL, a powerful database that stores data in tables (like Excel sheets).

2. The Librarian (Drizzle ORM)

You don't talk to the Filing Cabinet directly; it speaks a complex language called SQL. Instead, we use a "Librarian" called Drizzle ORM. You tell the Librarian: "Get me the character named Airi," and the Librarian translates that into SQL and fetches the data for you.

3. The Reception Desk (The API)

This is the front door. We use a framework called Hono to listen for requests.

How to Use: Defining a Character

In this system, "using" the server mostly means defining Schemas. A schema is a blueprint. It tells the database what a "Character" actually looks like.

Let's look at how we define a Character in apps/server/src/schemas/characters.ts.

import { pgTable, text, integer } from 'drizzle-orm/pg-core'

// We define a table called 'characters'
export const character = pgTable('characters', {
  
  // Every character has a unique ID
  id: text('id').primaryKey(),
  
  // They have a creator (the user who made them)
  creatorId: text('creator_id').notNull(),
  
  // They have stats like 'likes'
  likesCount: integer('likes_count').default(0),
})

Explanation: This code creates a rule. It says: "Every character in our world MUST have an ID, a Creator, and a Like Count." If you try to save a character without a Creator, the Librarian (Drizzle) will stop you.

Defining Capabilities

Characters are more than just names; they have skills. We store these as JSON data (flexible text objects).

export const characterCapabilities = pgTable('character_capabilities', {
  id: text('id').primaryKey(),
  
  // Link this skill to a specific character
  characterId: text('character_id').notNull(),

  // What kind of skill is it? (e.g., 'coding', 'painting')
  type: text('type').notNull(),

  // The details of the skill
  config: jsonb('config').notNull(),
})

Explanation: This allows our Cognitive Brain to look up what a character can do. If the Brain sees a capability of type coding, it knows it can write code.

Internal Implementation: The Request Lifecycle

What happens when your phone app tries to sync chat history?

The Flow of Data

sequenceDiagram participant App as Mobile App participant API as Hono Server (Reception) participant Service as Chat Service participant DB as Database (Filing Cabinet) App->>API: POST /api/chats/sync (Here is my new message) Note right of API: API checks your ID card (Auth) API->>Service: "Please save this message." Service->>DB: "Insert into chat_history..." DB-->>Service: "Saved successfully!" Service-->>App: "Done. Here are messages you missed from the PC."

Deep Dive: The Server Setup

The server is an application that runs 24/7. It starts in apps/server/src/app.ts.

1. Connecting to the Database

First, the application wakes up and connects to the storage.

// Inside createApp() function
const db = injeca.provide('services:db', {
  build: async ({ dependsOn }) => {
    // Connect to the database URL
    const dbInstance = createDrizzle(process.env.DATABASE_URL)
    
    // Ensure the tables exist
    await migrateDatabase(dbInstance)
    
    return dbInstance
  },
})

Explanation: This code ensures that before the server starts listening for users, the Filing Cabinet is open and organized (migrateDatabase).

2. The Receptionist (Routing)

The server needs to know where to send different requests. We use Routes to organize this.

function buildApp({ chatService }) {
  return new Hono()
    // If the request starts with /api/chats...
    .route('/api/chats', createChatRoutes(chatService))
    
    // If the request is for health check...
    .get('/health', c => c.json({ status: 'ok' }))
}

Explanation: This acts like a signpost.

3. Handling a Request (The Logic)

Let's look at apps/server/src/routes/chats.ts to see how a chat sync request is actually handled.

export function createChatRoutes(chatService: ChatService) {
  return new Hono()
    // Protect this route: Only logged-in users allowed!
    .use('*', authGuard)
    
    .post('/sync', async (c) => {
      // 1. Get the user from the session
      const user = c.get('user')

      // 2. Read the data sent by the App
      const body = await c.req.json()

      // 3. Ask the Chat Service to do the heavy lifting
      const result = await chatService.syncChat(user.id, body)
      
      // 4. Send the result back
      return c.json(result)
    })
}

Explanation:

  1. Security (authGuard): We check the ID card first. Strangers cannot read your chats.
  2. Input: We grab the JSON data (the new messages).
  3. Service Call: We pass the work to chatService. The route itself doesn't know how to save to the database; it just delegates the task.

Why is this "Beginner Friendly"?

You might notice we use a lot of Abstraction Layers:

While this seems like extra work, it makes the code much safer. If we change the database from PostgreSQL to MySQL in the future, we only have to retrain the "Librarian" (Drizzle), and the rest of the app doesn't need to change at all.

Summary

The Central Data & Identity Server is the backbone of the airi ecosystem.

  1. It provides Identity: You are the same user on every device.
  2. It provides Persistence: Your characters and chats are saved forever in the database.
  3. It uses Schemas to define the rules of the world (what a character looks like).

Now that we have a secure place to store our AI's memories, we can start giving it more advanced senses. It's time to let our AI hear the world.

Next Chapter: Sensory Audio Processing (Hearing)


Generated by Code IQ