AI Agents in Twenty CRM: Complete Technical Architecture and Implementation Guide

Twenty CRM treats AI agents as first-class metadata objects managed through NestJS services and GraphQL mutations, enabling workspace administrators to configure LLM assistants that users invoke via persistent chat threads.

Twenty CRM (twentyhq/twenty) implements a comprehensive AI agent system that embeds intelligent assistants directly into the CRM workflow. Unlike simple API integrations, the platform stores AI agents in Twenty CRM as core metadata entities, providing full CRUD capabilities through a type-safe GraphQL API and React frontend components.

Agent Metadata Architecture

Database Schema and Entity Definition

At the foundation of Twenty's AI system lies the AgentEntity class, which defines the database schema for agent storage. Located in packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts, this entity extends SyncableEntity to ensure workspace-wide consistency across tenant migrations.

The entity captures essential LLM configuration parameters:

  • name and label for identification
  • prompt for system instructions
  • modelId specifying the provider (e.g., "openai/gpt-4.1")
  • responseFormat defining output structure
  • Optional icon and description for UI representation

By extending SyncableEntity, the agent definition participates in Twenty's workspace migration system, allowing version-controlled deployment across different environments.

Backend Services and GraphQL API

AgentService Implementation

All agent lifecycle operations flow through AgentService in packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.service.ts. This service implements standardized metadata patterns including findManyAgents, findOneAgentById, createOneAgent, updateOneAgent, and deleteOneAgent.

The createOneAgent method demonstrates the platform's migration-aware approach:

// packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.service.ts
async createOneAgent(
  input: CreateAgentInput & { isCustom: boolean },
  workspaceId: string,
): Promise<FlatAgentWithRoleId> {
  // 1️⃣ Resolve application & role
  // 2️⃣ Convert DTO → flat‑agent representation
  const { flatAgentToCreate, flatRoleTargetToCreate } =
    fromCreateAgentInputToFlatAgent({ ... });

  // 3️⃣ Run workspace‑migration validation & apply
  const result = await this.workspaceMigrationValidateBuildAndRunService
    .validateBuildAndRunWorkspaceMigration({ ... });

  // 4️⃣ Retrieve the newly created flat‑agent from cache
  const createdAgent = findFlatEntityByIdInFlatEntityMapsOrThrow({ ... });

  return { ...createdAgent, roleId: flatRoleTargetToCreate?.roleId ?? null };
}

This implementation validates input against workspace migrations before persisting, ensuring schema consistency across the multi-tenant architecture.

GraphQL Resolver Interface

The AgentResolver in packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.resolver.ts exposes these operations through the GraphQL layer. It forwards Data Transfer Objects (DTOs) directly to AgentService, providing mutations like createOneAgent and queries like agents without complex business logic duplication.

Chat Thread Execution Model

When users interact with an agent, Twenty creates a structured conversation context through three hierarchical entities: AgentChatThreadEntity, AgentTurnEntity, and AgentMessageEntity.

Thread and Message Management

The AgentChatService in packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat.service.ts orchestrates conversation flow. It handles thread creation, message persistence, and LLM invocation coordination.

The addMessage method manages the complex state transitions:

// packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat.service.ts
async addMessage({
  threadId,
  uiMessage,
  agentId,
  turnId,
}: {
  threadId: string;
  uiMessage: Omit<ExtendedUIMessage, 'id'>;
  agentId?: string;
  turnId?: string;
}) {
  // Auto‑create a turn if none supplied
  if (!turnId) {
    const turn = this.turnRepository.create({ threadId, agentId: agentId ?? null });
    const savedTurn = await this.turnRepository.save(turn);
    turnId = savedTurn.id;
  }

  const message = this.messageRepository.create({
    threadId,
    turnId,
    role: uiMessage.role as AgentMessageRole,
    agentId: agentId ?? null,
  });
  const savedMessage = await this.messageRepository.save(message);

  // Persist any UI parts (files, tool calls)
  if (uiMessage.parts?.length) {
    const dbParts = mapUIMessagePartsToDBParts(uiMessage.parts, savedMessage.id);
    await this.messagePartRepository.save(dbParts);
  }

  return savedMessage;
}

This architecture supports rich message content through AgentMessagePartEntity, enabling file attachments and tool call sequences within conversations.

Title Generation Service

Twenty automatically generates human-readable thread titles via AgentTitleGenerationService in packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-title-generation.service.ts, invoked when the first user message arrives. This eliminates manual thread naming while maintaining conversation organization.

Frontend Integration

Settings Management Interface

Administrators configure agents through the Settings → AI → Agents interface. The SettingsAIAgentsTable component in packages/twenty-front/src/pages/settings/ai/components/SettingsAIAgentsTable.tsx retrieves agent lists using the generated GraphQL query FindManyAgentsDocument:

import { useQuery } from '@apollo/client';
import { FindManyAgentsDocument } from '@/generated-metadata/graphql';

export const SettingsAIAgentsTable = () => {
  const { data, loading, error } = useQuery(FindManyAgentsDocument);

  if (loading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;

  return (
    <Table>
      {data?.agents?.map(agent => (
        <tr key={agent.id}>
          <td>{agent.label}</td>
          <td>{agent.modelId}</td>
          <td>{agent.description}</td>
        </tr>
      ))}
    </Table>
  );
};

Agent creation and editing utilize SettingsAIAgentForm in packages/twenty-front/src/pages/settings/ai/forms/components/SettingsAIAgentForm.tsx, which maps form fields to the CreateAgentInput GraphQL type.

Conversation Initialization Hooks

End-users trigger agents through the useCreateAgentChatThread hook located in packages/twenty-front/src/modules/ai/hooks/useCreateAgentChatThread.ts:

// packages/twenty-front/src/modules/ai/hooks/useCreateAgentChatThread.ts
export const useCreateAgentChatThread = () => {
  const [createThread] = useMutation(CreateOneAgentChatThreadDocument);
  return async (agentId: string) => {
    const { data } = await createThread({ variables: { agentId } });
    return data?.createOneAgentChatThread?.id;
  };
};

This hook invokes the CreateOneAgentChatThread mutation, which instantiates an AgentChatThreadEntity linked to the specified agentId.

Practical Implementation Examples

Creating an Agent via GraphQL

Developers can programmatically create agents using the type-safe GraphQL API:

mutation CreateOneAgent($input: CreateAgentInput!) {
  createOneAgent(input: $input) {
    id
    name
    label
    prompt
    modelId
    responseFormat {
      type
    }
    isCustom
  }
}

Using the generated TypeScript client:

import { createOneAgent } from '@/generated-metadata/graphql';

await createOneAgent({
  variables: {
    input: {
      name: 'sales-assistant',
      label: 'Sales Assistant',
      prompt: 'You are a helpful sales assistant. Answer concisely.',
      modelId: 'openai/gpt-4.1',
      responseFormat: { type: 'text' },
      isCustom: true,
    },
  },
});

Starting a Conversation

The following React component demonstrates initiating a chat thread and sending the first message:

import { useCreateAgentChatThread } from '@/modules/ai/hooks/useCreateAgentChatThread';
import { useMutation } from '@apollo/client';
import { AddAgentMessageDocument } from '@/generated-metadata/graphql';

export const ChatBox = ({ agentId }: { agentId: string }) => {
  const createThread = useCreateAgentChatThread();
  const [addMessage] = useMutation(AddAgentMessageDocument);

  const startConversation = async (text: string) => {
    const threadId = await createThread(agentId);
    await addMessage({
      variables: {
        threadId,
        uiMessage: { role: 'user', content: text },
      },
    });
  };

  // UI omitted
};

Summary

  • AI agents in Twenty CRM persist as metadata entities via AgentEntity, enabling version-controlled configuration across workspaces.
  • AgentService and AgentResolver provide the backend CRUD operations and GraphQL API surface for agent management.
  • AgentChatService manages conversational state through threads, turns, and messages, supporting complex interactions with file attachments and tool calls.
  • The React frontend consumes these APIs through generated GraphQL documents and custom hooks like useCreateAgentChatThread.
  • All operations integrate with Twenty's workspace migration system, ensuring schema consistency in multi-tenant deployments.

Frequently Asked Questions

How does Twenty CRM store AI agent configurations?

Twenty CRM stores AI agents in the agent database table defined by AgentEntity in packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts. The entity captures the agent's name, prompt, model ID, response format, and optional metadata like icons and descriptions. Because it extends SyncableEntity, these configurations propagate through workspace migrations to maintain consistency across tenant environments.

What GraphQL mutations are available for managing AI agents?

The platform exposes createOneAgent, updateOneAgent, and deleteOneAgent mutations through the AgentResolver, along with queries like agents and searchAgents. These endpoints accept strongly-typed inputs such as CreateAgentInput and forward requests to AgentService, which handles validation and workspace migration integration before persisting changes.

How does the chat thread system handle message persistence?

When users send messages, AgentChatService in packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat.service.ts creates AgentTurnEntity and AgentMessageEntity records. The service auto-generates turns if not provided, persists message content and optional parts (files or tool calls) via AgentMessagePartEntity, and coordinates with the AI SDK to stream LLM responses back to the client.

Can developers extend the AI agent system with custom models?

Yes, the modelId field in AgentEntity accepts arbitrary provider strings (e.g., "openai/gpt-4.1"), and the modelConfiguration JSON field allows additional provider-specific parameters. The execution layer uses the AI SDK (such as @ai-sdk/openai) to invoke the specified model, meaning any model supported by the underlying SDK integration can be configured through the standard agent creation interface.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →