# Introduction
In this pullrequest have been migrated to the flat standard entities:
Role, Agent and RoleTargets.
## What happens
- Removed createStandardMorph tool util in favor of dynamic typing of
`createMorphOrRelationStandardField`
- Implemented a command to remove standard agents and their role that
has been removed in https://github.com/twentyhq/twenty/pull/16513 also
added a default role target to data manipulator role to the only
remaining agent
- Implemented an agent deleteMany service handler
- moves applicationRoleId to application entity
- add new `APPLICATION` FieldActorSource and `APPLICATION`
JwtTokenTypeEnum value
- create a new token with applicationId when executing a function
- when applicationId is in token, check for application.defaultRole
permissions
-use twenty-shared types in `twenty-sdk/application`
- create a new import from generate called "Twenty" that you can use
directly without having to set TWENTY_API_KEY AND TWENTY_API_URL (keep
metadata or core parameter only)
- provide to serverless unique one time BEARER TOKEN to run it
Result
<img width="977" height="566" alt="image"
src="https://github.com/user-attachments/assets/e78428a0-5b13-4975-aa13-58ee3b32450c"
/>
<img width="910" height="596" alt="image"
src="https://github.com/user-attachments/assets/6ec72bf5-7655-4093-a45e-ad269595a324"
/>
<img width="741" height="568" alt="image"
src="https://github.com/user-attachments/assets/7683944c-fd79-4417-8fb2-8e4815cc112f"
/>
## Introduction
When migrating a v1 index name to v2 it might collide with an existing
v2 index
In this case we remove both the v1 metadata and pg index
In case of a metadata and pg_index desync this might occur too late in
the process that's why we have two fallback
One computing the v1 deletion from the metadata and another one in the
catch block of the v1 to migration transaction commit
## Summary
- Add a context usage indicator to the AI chat interface inspired by
Vercel's AI SDK Context component
- Display token consumption, context window utilization percentage, and
estimated cost in credits
- Show a circular progress ring with percentage, revealing detailed
breakdown on hover
## Changes
### Backend
- Stream usage metadata (tokens, model config) via `messageMetadata`
callback in `agent-chat-streaming.service.ts`
- Return model config from `chat-execution.service.ts`
- Add usage and model types to `ExtendedUIMessage` metadata
### Frontend
- New `ContextUsageProgressRing` component - circular SVG progress
indicator
- New `AIChatContextUsageButton` component with hover card showing:
- Progress bar with used/total tokens
- Input/output token counts with credit costs
- Total credits consumed
- Track cumulative usage in Recoil state (`agentChatUsageState`)
- Reset usage when creating new chat thread
- Integrate button into `AIChatTab`
## Test plan
- [ ] Open AI chat and send a message
- [ ] Verify the context usage button appears with percentage
- [ ] Hover over the button to see detailed breakdown
- [ ] Verify credits are calculated correctly
- [ ] Create a new chat thread and verify usage resets to 0
# Introduction
Related https://github.com/twentyhq/core-team-issues/issues/1910
From now on the upgrade won't integrate any sync metadata as it's going
to be deprecated very soon
Any updates to about to removes workspace-entity or standard flat entity
will require a dedicated upgrade command, what we've already started
doing during the 1.13 sprint, until we have totally migrated the v2 to
be workspace agnostic
# TimelineActivity migration to morph
- Creates `timelineActivities2` relations on Company, Dashboard, Note,
Opportunity, Person, Task, Workflow, WorkflowRun, and WorkflowVersion
entities with proper metadata and cascade delete behavior.
It was required to create standard fields as well since the
mapObjectMetadataByUniqueIdentifier needs it. otherwise the fields won't
be considered
- Feature Flag `IS_TIMELINE_ACTIVITY_MIGRATED` necessary to have the two
states in parallel. It is used as a stamp once the migration has been
run
- Migration is done using the coreDataSource. Why ?
even though is unsafe to use, the first implementation of the migration
took forever on each workspace. See [this
commit](https://github.com/twentyhq/twenty/pull/15652/commits/477011e8d7d4c580f79ba7ec4a8fb002a3ec86b2)
The plan for this complex migration is as follows :

Note: we will need to rename fields in the release 1.12 (there is no
easy way to do all this in one release)
## Context
Following https://github.com/twentyhq/twenty/pull/16399
Now using the new global orm manager everywhere and returning a
GlobalDatasource/WorkspaceDatasource based on a feature flag.
This means we now need to wrap all our ORM calls within
executeInWorkspaceContext callback (at least for now) so the global
datasource can dynamically hydrate its context via the new store (the
global datasource does not store anything related to workspaces as it is
now a unique singleton). If feature flag is off it still uses local data
stored in the workspace datasource.
# Introduction
In this PR we've introduced the type safe generation tooling for
standard view, view fields, view filters and view groups.
Also generated the ids before such as what was done for object and
fields, it's still temporary as will be replaced by universalIdentifier
mapping afterwards
## Task views
In this PR only implemented the `task` views and view groups, filters
and fields in order to battle test all the generation tools
Will create a dedicated PR that will implement all the remaining views.
# Introduction
Related https://github.com/twentyhq/core-team-issues/issues/1995
In this PR we're introducing the standard index tools for their
declaration
Also adding a common typing in order to have a simily consistency across
standard builders
Will implement remaining standard index builder in an other PR
Fixes https://github.com/twentyhq/twenty/issues/15925
- update field metadata update logic
- uniformize the way index are named
- command to migrate v1-named unique index
- add integration testing
---------
Co-authored-by: prastoin <paul@twenty.com>
First PR to implement application tokens
- add new application role in twenty-server
- move duplicated constants and types to twenty-shared
- will add role configuration utils into twenty-sdk in another PR
# Introduction
Making columns nullable instead of required + metadata on the fly
migration in typeorm migration that could affect other breaking change
migrations to be run
# Introduction
Related to https://github.com/twentyhq/core-team-issues/issues/1995
This PR introduces the basis of the `twentyStandard` application as code
on demand, it's highly tied to `ids` where it will becomes workspace
agnostic following the builder and runner `universalIdentifier` refactor
later.
The goal here to allow computing the `allFlatEntityMaps` `to` of the
`twentyStandard` application on a empty workspace ( workspace creation
). Allowing installing the twenty standard app through a workspace
migration instead of passing by the sync metadata
Nothing done will be run in production for the moment if it's not the
small validation refactor we've introduced
Please note that everything introduced here will be replaced at some
point by a twenty app instance when the twenty sdk is mature enough to
handle of the edge cases we need here
## How we've proceeded
We've been iterating over every workspace entity both objects and their
fields, and transpiled them to flatEntity.
Being sure we migrate the defaultValue, settings and so on accordingly.
We've also compute all the ids in prior of the whole entities
computation so we don't face any hoisting issue.
## Current state
At the moment only handling all of the 29 standard objects and their
fields
Settings a unique universalIdentifier for all of them
Will come views, agent role targets and so on later
## `workspace:compute-twenty-standard-migration` command
This command allow generating a workspace migration that will result in
installing the twenty standard app in an empty workspace
It's temporary and aims to allow debugging for the moment we might not
keep it in the future as it is right now
It contains debug writeFileSync which is expected no worries greptile
## `LabelFieldMetadataIdentifierId`
Small refactor allowing defining the label identifier field metadata id
of a uuid field metadata type for system object, as some of our standard
object don't have a name field and don't aim to
Also please note that we might remove this build options later in the
sake of the currently installed universal identifier application that we
could compare with the deterministic twenty standard one
## `runFlatFieldMetadataValidators`
Deprecated this pattern which was redundant and not v2 friendly pattern
## Current errors that will address in upcoming PR
Current standard objects and fields metadata does not pass the
validation that we have in place, as historically the sync metadata
would directly consume the repositories and would just ignore the
validation. This is about to change.
Will handle the below errors in dedicated PRs as they will required
upgrade commands in order to migrate the data, or will handle that from
the sync metadata instead still to be determined but nothing critical
here
- camel case field metadata name
- options label invalid format
```json
{
"status": "fail",
"report": {
"fieldMetadata": [
{
"status": "fail",
"errors": [
{
"code": "INVALID_FIELD_INPUT",
"message": "Name should be in camelCase",
"userFriendlyMessage": {
"id": "P+jdmX",
"message": "Name should be in camelCase"
},
"value": "iCalUID"
}
],
"flatEntityMinimalInformation": {
"id": "68dd83cd-92c8-4233-bb28-47939bab6124",
"name": "iCalUID",
"objectMetadataId": "11c16ab6-9176-439e-a2db-a12c5a58a524"
},
"type": "create_field"
},
{
"status": "fail",
"errors": [
{
"code": "INVALID_FIELD_INPUT",
"message": "Value must be in UPPER_CASE and follow snake_case \"email\"",
"userFriendlyMessage": {
"id": "UBPzFQ",
"message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
"values": {
"sanitizedValue": "email"
}
},
"value": "email"
},
{
"code": "INVALID_FIELD_INPUT",
"message": "Value must be in UPPER_CASE and follow snake_case \"sms\"",
"userFriendlyMessage": {
"id": "UBPzFQ",
"message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
"values": {
"sanitizedValue": "sms"
}
},
"value": "sms"
}
],
"flatEntityMinimalInformation": {
"id": "e3caaf2a-e07d-4146-8dfc-9eef904e82c9",
"name": "type",
"objectMetadataId": "4b777de5-4c7b-4af4-9b92-655c0f87512b"
},
"type": "create_field"
},
{
"status": "fail",
"errors": [
{
"code": "INVALID_FIELD_INPUT",
"message": "Value must be in UPPER_CASE and follow snake_case \"incoming\"",
"userFriendlyMessage": {
"id": "UBPzFQ",
"message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
"values": {
"sanitizedValue": "incoming"
}
},
"value": "incoming"
},
{
"code": "INVALID_FIELD_INPUT",
"message": "Value must be in UPPER_CASE and follow snake_case \"outgoing\"",
"userFriendlyMessage": {
"id": "UBPzFQ",
"message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
"values": {
"sanitizedValue": "outgoing"
}
},
"value": "outgoing"
}
],
"flatEntityMinimalInformation": {
"id": "d96233a4-93be-45ea-9548-3b50f3c700cf",
"name": "direction",
"objectMetadataId": "480a648a-d2e5-482a-992f-ef053e1b4bb0"
},
"type": "create_field"
},
{
"status": "fail",
"errors": [
{
"code": "INVALID_FIELD_INPUT",
"message": "Value must be in UPPER_CASE and follow snake_case \"from\"",
"userFriendlyMessage": {
"id": "UBPzFQ",
"message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
"values": {
"sanitizedValue": "from"
}
},
"value": "from"
},
{
"code": "INVALID_FIELD_INPUT",
"message": "Value must be in UPPER_CASE and follow snake_case \"to\"",
"userFriendlyMessage": {
"id": "UBPzFQ",
"message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
"values": {
"sanitizedValue": "to"
}
},
"value": "to"
},
{
"code": "INVALID_FIELD_INPUT",
"message": "Value must be in UPPER_CASE and follow snake_case \"cc\"",
"userFriendlyMessage": {
"id": "UBPzFQ",
"message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
"values": {
"sanitizedValue": "cc"
}
},
"value": "cc"
},
{
"code": "INVALID_FIELD_INPUT",
"message": "Value must be in UPPER_CASE and follow snake_case \"bcc\"",
"userFriendlyMessage": {
"id": "UBPzFQ",
"message": "Value must be in UPPER_CASE and follow snake_case \"{sanitizedValue}\"",
"values": {
"sanitizedValue": "bcc"
}
},
"value": "bcc"
}
],
"flatEntityMinimalInformation": {
"id": "961c598e-67c3-452d-8bb2-b92c0bc64404",
"name": "role",
"objectMetadataId": "8af8a13c-ff97-4cd3-b70d-52a7dc2924b4"
},
"type": "create_field"
},
{
"status": "fail",
"errors": [
{
"code": "INVALID_FIELD_INPUT",
"message": "Label must not contain a comma",
"userFriendlyMessage": {
"id": "k731jp",
"message": "Label must not contain a comma"
},
"value": "Commas and dot (1,234.56)"
},
{
"code": "INVALID_FIELD_INPUT",
"message": "Label must not contain a comma",
"userFriendlyMessage": {
"id": "k731jp",
"message": "Label must not contain a comma"
},
"value": "Spaces and comma (1 234,56)"
},
{
"code": "INVALID_FIELD_INPUT",
"message": "Label must not contain a comma",
"userFriendlyMessage": {
"id": "k731jp",
"message": "Label must not contain a comma"
},
"value": "Dots and comma (1.234,56)"
}
],
"flatEntityMinimalInformation": {
"id": "7fa20caf-2597-42e3-84e5-15a91b125b9b",
"name": "numberFormat",
"objectMetadataId": "a6974302-9e72-461c-aa09-9390f4ff16fc"
},
"type": "create_field"
}
],
"objectMetadata": [],
"view": [],
"viewField": [],
"viewGroup": [],
"index": [],
"serverlessFunction": [],
"cronTrigger": [],
"databaseEventTrigger": [],
"routeTrigger": [],
"viewFilter": [],
"role": [],
"roleTarget": [],
"agent": []
}
}
```
# Introduction
Migrating `pageLayoutTab` to the v2 engine
- Types and constants
- Builder and validate
- Runner
Introduced a new `StrictSyncableEntity` that enforces that
`universalIdentifier` and `applicationId` are defined
As these entities are brand new we could enforce this rule already
This still requires a migration command to associate the existing
entities to custom workspace application instance and define
universalIdentifier
Handled retro-comp of the migration through a migration as upgrade
command fallback
---------
Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
# Introduction
On production facing a migration error with
UpdateRoleTargetsUniqueConstraint1764329720503
```ts
Migration "UpdateRoleTargetsUniqueConstraint1764329720503" failed, error: could not create unique index "IDX_ROLE_TARGETS_UNIQUE_AGENT"
query: ROLLBACK
Error during migration run:
QueryFailedError: could not create unique index "IDX_ROLE_TARGETS_UNIQUE_AGENT"
at PostgresQueryRunner.query (/Users/paulrastoin/ws/twenty/node_modules/typeorm/driver/postgres/PostgresQueryRunner.js:219:19)
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
at async UpdateRoleTargetsUniqueConstraint1764329720503.up (/Users/paulrastoin/ws/twenty/packages/twenty-server/dist/database/typeorm/core/migrations/common/1764329720503-update-role-targets-unique-constraint.js:15:9)
at async MigrationExecutor.executePendingMigrations (/Users/paulrastoin/ws/twenty/node_modules/typeorm/migration/MigrationExecutor.js:225:17)
at async DataSource.runMigrations (/Users/paulrastoin/ws/twenty/node_modules/typeorm/data-source/DataSource.js:265:35)
at async Object.handler (/Users/paulrastoin/ws/twenty/node_modules/typeorm/commands/MigrationRunCommand.js:68:13) {
query: 'ALTER TABLE "core"."roleTargets" ADD CONSTRAINT "IDX_ROLE_TARGETS_UNIQUE_AGENT" UNIQUE ("workspaceId", "agentId")',
parameters: undefined,
driverError: error: could not create unique index "IDX_ROLE_TARGETS_UNIQUE_AGENT"
at /Users/paulrastoin/ws/twenty/node_modules/pg/lib/client.js:526:17
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
at async PostgresQueryRunner.query (/Users/paulrastoin/ws/twenty/node_modules/typeorm/driver/postgres/PostgresQueryRunner.js:184:25)
at async UpdateRoleTargetsUniqueConstraint1764329720503.up (/Users/paulrastoin/ws/twenty/packages/twenty-server/dist/database/typeorm/core/migrations/common/1764329720503-update-role-targets-unique-constraint.js:15:9)
at async MigrationExecutor.executePendingMigrations (/Users/paulrastoin/ws/twenty/node_modules/typeorm/migration/MigrationExecutor.js:225:17)
at async DataSource.runMigrations (/Users/paulrastoin/ws/twenty/node_modules/typeorm/data-source/DataSource.js:265:35)
at async Object.handler (/Users/paulrastoin/ws/twenty/node_modules/typeorm/commands/MigrationRunCommand.js:68:13) {
length: 314,
severity: 'ERROR',
code: '23505',
```
As several agent has duplicated role target in database
```sql
SELECT constraint_name, COUNT(*) AS duplicate_groups, SUM(duplicate_count - 1) AS rows_to_delete
FROM (
SELECT 'IDX_ROLE_TARGET_UNIQUE_API_KEY' AS constraint_name, COUNT(*) AS duplicate_count
FROM "core"."roleTargets" rt
WHERE rt."apiKeyId" IS NOT NULL
GROUP BY rt."workspaceId", rt."apiKeyId"
HAVING COUNT(*) > 1
UNION ALL
SELECT 'IDX_ROLE_TARGET_UNIQUE_AGENT', COUNT(*)
FROM "core"."roleTargets" rt
WHERE rt."agentId" IS NOT NULL
GROUP BY rt."workspaceId", rt."agentId"
HAVING COUNT(*) > 1
UNION ALL
SELECT 'IDX_ROLE_TARGET_UNIQUE_USER_WORKSPACE', COUNT(*)
FROM "core"."roleTargets" rt
WHERE rt."userWorkspaceId" IS NOT NULL
GROUP BY rt."workspaceId", rt."userWorkspaceId"
HAVING COUNT(*) > 1
) AS all_duplicates
GROUP BY constraint_name;
```
with constraint name, duplicated_groups, row_to_delete
`IDX_ROLE_TARGET_UNIQUE_AGENT 2507 7229`
Please note that only active or suspended workspaces contains duplicated
role target
## Fix
Introduced an upgrade command that will only keep the latest inserted
role target
Swallowing migration error on typeorm atomic migration ( still required
for self host new instances etc )
```ts
[Nest] 91886 - 12/03/2025, 2:56:28 PM LOG [DeduplicateRoleTargetsCommand] Running command on workspace SOME_WORKSPACE_ID 2587/2587
flatFieldMetadataMaps,flatIndexMaps,flatObjectMetadataMaps out of 298
query: SELECT version();
[Nest] 91886 - 12/03/2025, 2:56:29 PM LOG [DeduplicateRoleTargetsCommand] Command completed!
```
## Test
Tested through an extract in local, tested both the upgrade command and
the swallowed migration
test
# Introduction
Closes https://github.com/twentyhq/core-team-issues/issues/1980
In this PR we migrate the agent from v1 to v2.
## New FlatRoleTargetByAgentIdMaps
Derivated the `flatRoleTargetMaps` to be building a
`flatRoleTargetByAgentIdMaps` to ease retrieving a roleId to associate
to an agent
## Coverage
Added strong coverage on both failing and successful CRU agents
operations
---------
Co-authored-by: Weiko <corentin@twenty.com>
2025-12-03 11:51:19 +01:00
Thomas TrompetteGitHubgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
- if >5000 workflows per hour, new ones should failed
- if >100 workflow per min, new ones should be set as not started.
Except manual trigger
- when enqueued, we check if there a not started workflows that may be
queued. If yes, we call the associated job
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
In this PR (1/3)
- introduce view.mainGroupByFieldMetadataId as the new reference
determining which fieldMetadataId is used in a grouped view, in order to
deprecate viewGroup.fieldMetadataId which creates inconsistencies.
view.mainGroupByFieldMetadataId is now filled at every view creation,
though not in use yet.
- Introduce a command to backfill view.mainGroupByFieldMetadataId for
existing views + delete all viewGroup.fieldMetadataId with a
fieldMetadataId that is not view.mainGroupByFieldMetadataId. (It should
concern 37 active workspaces)
- Temporarily disable the option to change a grouped view's
fieldMetadataId as for now it creates inconsistencies. This feature can
be reintroduced when we have done the full migration.
In a next PR
- (2/3) use view.mainGroupByFieldMetadataId instead of
viewGroup.fieldMetadataId. In FE we may keep viewGroup.fieldMetadataId
as a state (TBD). View groups will now be created / deleted as a side
effect of view's mainGroupByFieldMetadataId update.
- (3/3) remove viewGroup.fieldMetadataId
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
Because of [this code I forget to
clean](https://github.com/twentyhq/twenty/pull/16217),
- __workspace created between 1.12.0 and 1.12.2 (from friday 28 nov PM >
monday 1 dec PM)__ have standard objects with empty string default value
set on related TEXT type column (but default value null on field
metadata) (ex: jobTitle on person)
- __custom objects created between 1.12.0 and 1.12.2 (from friday 28 nov
PM > monday 1 dec PM)__ have "name" TEXT field with empty string default
value set + NOT NULL constraint on column
Command cleans data and updates table structure
# Introduction
close https://github.com/twentyhq/core-team-issues/issues/1930
close https://github.com/twentyhq/core-team-issues/issues/1929
Migrating role and roleTarget entities to the v2 core engine, allowing
v2 caching leverage and allow migrating agent to v2 that needs role
target in prior
After agent we should be able to pass twenty standard app totally though
workspace migration
## Role target assignation
Please note that role target have 3 creation entrypoints:
- Agent
- User workspace
- ApiKey
Refactored all 3 of them to pass through a new role-target.service.ts
that consumes the v2 under the hood.
---------
Co-authored-by: Weiko <corentin@twenty.com>
## Summary
This PR introduces a comprehensive agent evaluation system and refactors
the AI module structure for better organization.
## Key Changes
### 🎯 Agent Evaluation System
- Added **Agent Turn Evaluation** entities, DTOs, and database schema
- New GraphQL mutations: `evaluateAgentTurn` and `runEvaluationInput`
- Added `evaluationInputs` field to Agent entity for storing test inputs
- New `AgentTurnGraderService` for automatic turn evaluation
- Added evaluation UI with new **Evals** and **Logs** tabs in agent
detail pages
### 🏗️ Entity & Module Refactoring
- Renamed `AgentChatMessage` → `AgentMessage` for clarity
- Consolidated chat entities: `AgentMessage`, `AgentTurn`, and
`AgentChatThread`
- Reorganized AI modules under `ai/` subdirectory structure
- Updated imports across codebase to reflect new module paths
### 🤖 New Agents & Roles
- Added **Dashboard Builder Agent** for dashboard creation and
management
- Added **Dashboard Manager Role** with appropriate permissions
- Updated role permissions to be more granular (users vs agents vs API
keys)
### 🔐 Permission System Updates
- Added `HTTP_REQUEST_TOOL` permission flag
- Updated Workflow Manager role permissions (restricted tool access)
- Enhanced permission flag types to differentiate between user/agent/API
key contexts
- Added `isRelevantForAgents`, `isRelevantForApiKeys`,
`isRelevantForUsers` to permission flags
### 📨 Message Role Enhancement
- Added `system` role to `AgentMessageRole` enum (alongside
user/assistant)
- Updated message handling to support system prompts
### 🎨 UI/UX Improvements
- New tabs in agent detail: **Evals** and **Logs**
- Added turn detail page: `/ai/agents/:agentId/turns/:turnId`
- Fixed text overflow in `SettingsListItemCardContent`
- Updated role applicability labels ("Assignable to Workspace Members")
### 🛠️ Technical Improvements
- Fixed Zod schema validation for UUID and Date fields (use string
validators)
- Updated `ToolRegistryService` to properly register HTTP tool with
permission flag
- Enhanced error handling in agent execution services
- Updated database migrations for new entity schema
## Database Migrations
- `1764210000000-add-system-role-to-agent-message.ts`
- `1764220000000-add-evaluation-inputs-to-agent.ts`
- `1764200000000-add-agent-turn-evaluation.ts`
- `1764100000000-refactor-agent-chat-entities.ts`
## Testing
- [ ] Agent evaluation flow tested
- [ ] Dashboard Builder agent tested
- [ ] Permission system validated
- [ ] UI tabs and navigation tested
- [ ] Database migrations run successfully
## Breaking Changes
⚠️ **Entity Rename**: `AgentChatMessage` renamed to `AgentMessage` -
GraphQL queries need updating
## Related Issues
<!-- Link any related issues here -->
## Screenshots
<!-- Add screenshots if applicable -->
# Introduction
We need to be able to create custom workspace application on all
workspaces, even pending and ongoing etc
Right now the upgrade devx only allows and expect active or suspended
workspace to be passed to runOnWorkspace.
## WorkspacesMigrationRunner
Created an intermediate class `WorkspacesMigrationRunner` that expect an
array `WorkspaceStatus` to be fetched for the current command to be run
on
The `ActiveOrSuspendedCommandRunner` statically passes both `SUSPENDED`
and `ACTIVE`, whereas the create workspace custom application passed all
the enum values
## DataSource
Workspace that are not fully init don't have a `workspace_schema` so
they don't have `dataSource`
Made a not very elegant check to see if current workspace we're about to
create dataSource on has one historically
Which means that dataSource is now optional, it had only one impact on
an existing command and the desired devx will become consuming existing
services that do not expect dataSource ( or at least yet )
We have introduced to new syncStage statuses:
`messageChannel.MESSAGES_IMPORT_SCHEDULED` and
`calendarChannel.CALENDAR_EVENTS_IMPORT_SCHEDULED`
We need to make sure all existing workspaces have it
## Overview
This PR replaces the dynamic agent handoff system with a more
predictable planning-based router that decides upfront how to handle
multi-agent coordination.
## Major Changes
### 🔄 Architecture Shift: Handoffs → Planning
**Removed:**
- `AgentHandoffEntity` and handoff tracking system
- `AgentHandoffService` and `AgentHandoffExecutorService`
- Dynamic agent-to-agent transfers during execution
- Handoff tool generation and description templates
**Added:**
- `AiRouterService` with two strategies: `simple` (single agent) and
`planned` (multi-agent)
- `AgentPlanExecutorService` for executing multi-step plans
- Plan validation (cycle detection, dependency resolution)
- `UnifiedRouterResult` type with discriminated union
### 🤖 New Standard Agents
Added two new specialized agents:
- **Researcher Agent**: Web search, fact-finding, competitive
intelligence
- **Code Agent**: TypeScript function generation for serverless
workflows
### 🏗️ Router Refactoring (Latest)
Split router responsibilities into focused services:
- `AiRouterStrategyDeciderService`: Decides simple vs planned strategy
- `AiRouterPlanGeneratorService`: Generates and validates execution
plans
- `AiRouterService`: Coordinates between services (reduced from 426→275
lines)
### ⚙️ Configuration Improvements
- Added `outputStrategy` to agent definitions (`direct` vs `synthesize`)
- Removed hardcoded special cases for workflow-builder
- Added `plannerModel` field to workspace entity
- Increased `MAX_STEPS` from 10 to 25 for complex workflows
### 📝 Agent Prompt Refinements
Significantly simplified prompts for better clarity:
- Workflow Builder: 51→36 lines
- Helper: 49→28 lines
- Data Manipulator: Enhanced with sorting guidance
### 🔍 Enhanced Debugging
- Plan reasoning and step count in data message parts
- Router debug info with token usage tracking
- Better logging throughout execution pipeline
## Benefits
1. **Simpler Mental Model**: Router decides upfront vs dynamic transfers
2. **Better Predictability**: Users see the plan before execution
3. **Cleaner Architecture**: SRP with focused services
4. **Configuration Over Code**: Agent behavior via config, not hardcoded
logic
5. **Plan Validation**: Catches invalid dependencies and cycles
## Migration Notes
- Database migration removes `agentHandoff` table
- Adds `plannerModel` column to workspace table
- No API breaking changes (agent endpoints unchanged)
## Testing
- Integration tests updated to remove handoff dependencies
- Agent tool test utilities simplified
- Plan validation covered by new logic
## Next Steps (Future PRs)
- Parallel execution of independent plan steps
- Dynamic re-planning based on results
- Plan caching for common routing patterns
- Error recovery strategies in plan executor
# Introduction
Two things:
- Enforcing non nullable workspace custom application Id for any
workspace
- Fixing front non editable data models following
https://github.com/twentyhq/twenty/pull/15911 that associate any custom
entities to an applicationId. The front was putting everything as
readonly when under an app ( we will have to handle the twenty standard
application in the future too )
## Fallback
### Migration
The non nullable migration will fail when released, that's why it's
being swallowed and re-run in an upgrade command post workspace custom
application creation for those that miss one. Allowing the migration to
pass in the end
The typeorm migration still need to exists for any new workspaces
### GetCurrentUser
In order to dynamically display isReadOnly in data model settings we're
fetching the workspaceCustomApplicationId through the `getCurrentUser`
If not fallback this endpoint would throw until we're handling existing
workspaces that do not have a custom workspace application
The fallback should be removed post release
# Introduction
Cleaner and fewer scope version of
https://github.com/twentyhq/twenty/pull/15745 ( removed sync-metadata
hack through, too ambitious migration and upgrade )
Please note that this PR won't have any interaction with the existing
sync-metadata
Which mean that the sync metadata does not update the standard entities
applicationId and universalIdentifier, and it won't we will deprecate it
on favor of a workspace migration aka twenty-standard app installation
## API Metadata
Any operation going through the api metadata nows automatically scope
the related entity to the workspace custom application instance. (
optionally passing an applicationId to allow current hacky implem of app
sync service )
We need to either ignore the tests or remove the cli status check from
the blocking status badges for a PR to be merged
## New workspace
Already handled in previous
https://github.com/twentyhq/twenty/pull/15625, when a workspace is
created it gets created a twenty standard and custom workspace instance
All his views and permissions will be prefilled to the its twenty
standard app instance with a specific universalIdentifier
## New universalIdentifier
At the contrary as before with standardIds, universalIdentifier are
unique for a given workspace
This means that createdAt field of both object company and opportunity
will have a unique universalIdentifier whereas they share the same
standardId
## FlatApplication
Introduced the flatApplication and cache. Will migrate existing
`MetadataName` to be `SyncableMetadataName` in a following PR
## What's next
Next we will describe a twenty standard app configuration as json that
will be used to generate a workspace migration that will be run instead
of the sync metadata, in a nutshell we aim to deprecated the sync
metadata
So we can standardize any entity to have a non nullable applicationId
and universalIdentifier
## Upgrade command
Introduced an upgrade command that will create a custom workspace
instance for any workspace that do not have one in order to align with
the new behavior when creating a new workspace
## Summary
This PR adds configurable response format support for AI agents,
allowing them to return either plain text or structured JSON data based
on a defined schema.
## Key Features
### 1. Agent Response Format Configuration
- Added `AgentResponseFormat` type supporting:
- `text`: Returns plain text responses (default)
- `json`: Returns structured JSON based on defined schema
- New `AgentResponseSchema` type moved to `twenty-shared/ai` for sharing
between frontend/backend
### 2. Settings UI
- New `SettingsAgentResponseFormat` component for configuring response
format
- Visual schema builder for defining JSON output structure
- Real-time validation and preview
- Integrated into agent settings tab
### 3. Workflow Integration
- AI Agent workflow action automatically uses agent's configured
response format
- Output schema dynamically generated from agent's response format
- Workflow variable picker shows structured fields for JSON responses
- Backward compatible with existing text-only agents
### 4. Backend Implementation
- Added `convertAgentSchemaToZod` utility to validate JSON responses
- Agent executor service handles both text and JSON generation
- Automatic agent creation/cloning when adding AI agent steps to
workflows
- Unique agent naming with conflict resolution
### 5. Database Migration
- Migration `1763622159656-update-agent-response-format.ts`
- Sets default `responseFormat` to `{"type":"text"}` for existing agents
- Updated all standard agents with proper response format
## Changes by Module
### Frontend (`twenty-front`)
- 🆕 `AgentResponseFormat` type
- 🆕 `SettingsAgentResponseFormat` component
- ✏️ Updated `WorkflowEditActionAiAgent` to support response format
configuration
- 🗑️ Removed deprecated `useAiAgentOutputSchema` hook and
`AiAgentOutputSchema` type
### Backend (`twenty-server`)
- 🆕 `AgentResponseFormat` type in agent entity
- 🆕 `convertAgentSchemaToZod` utility for schema validation
- ✏️ Updated `AiAgentExecutorService` to handle both text and JSON
generation
- ✏️ Updated `WorkflowSchemaWorkspaceService` to generate output schema
from agent config
- ✏️ Enhanced `WorkflowVersionStepOperationsWorkspaceService` with agent
creation/cloning
- 🆕 Agent naming constants for conflict resolution
### Shared (`twenty-shared`)
- 🆕 `AgentResponseSchema` type
- 🆕 `ModelConfiguration` type moved to shared package
- Updated exports in `ai/index.ts`
## Code Quality
- Removed useless comments following code style guidelines
- All linter checks passed
- Type-safe implementation with proper TypeScript types
## Testing
- ✅ Database migration tested
- ✅ Agent creation/cloning in workflows verified
- ✅ Response format switching (text ↔ JSON) validated
- ✅ Backward compatibility with existing agents confirmed
## Migration Notes
- Existing agents will have `responseFormat: {type: 'text'}` set
automatically
- No breaking changes - all existing functionality preserved
- Agents can be updated to use JSON format through settings UI