Files
upstream-mirror e7da97194a
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s
snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
2026-09-17 23:28:13 +05:00

241 lines
9.7 KiB
Plaintext

---
title: Skills & Agents
description: Define AI skills and agents for your app.
icon: "robot"
---
<Warning>
Skills and agents are currently in alpha. The feature works but is still evolving.
</Warning>
Apps can define AI capabilities that live inside the workspace — reusable skill instructions and agents with custom system prompts.
<AccordionGroup>
<Accordion title="defineSkill" description="Define AI agent skills">
Skills define reusable instructions and capabilities that AI agents can use within your workspace. Use `defineSkill()` to define skills with built-in validation:
```ts src/skills/example-skill.ts
import { defineSkill } from 'twenty-sdk/define';
export default defineSkill({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'sales-outreach',
label: 'Sales Outreach',
description: 'Guides the AI agent through a structured sales outreach process',
icon: 'IconBrain',
content: `You are a sales outreach assistant. When reaching out to a prospect:
1. Research the company and recent news
2. Identify the prospect's role and likely pain points
3. Draft a personalized message referencing specific details
4. Keep the tone professional but conversational`,
});
```
Key points:
- `name` is a unique identifier string for the skill (kebab-case recommended).
- `label` is the human-readable display name shown in the UI.
- `content` contains the skill instructions — this is the text the AI agent uses.
- `icon` (optional) sets the icon displayed in the UI.
- `description` (optional) provides additional context about the skill's purpose.
</Accordion>
<Accordion title="defineAgent" description="Define AI agents with custom prompts">
Agents are AI assistants that live inside your workspace. Use `defineAgent()` to create agents with a custom system prompt:
```ts src/agents/example-agent.ts
import { defineAgent } from 'twenty-sdk/define';
export default defineAgent({
universalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123',
name: 'sales-assistant',
label: 'Sales Assistant',
description: 'Helps the sales team draft outreach emails and research prospects',
icon: 'IconRobot',
prompt: 'You are a helpful sales assistant. Help users with their questions and tasks.',
});
```
Key points:
- `name` is the unique identifier string for the agent (kebab-case recommended).
- `label` is the display name shown in the UI.
- `prompt` is the system prompt that defines the agent's behavior.
- `description` (optional) provides context about what the agent does.
- `icon` (optional) sets the icon displayed in the UI.
- `modelId` (optional) overrides the default AI model used by the agent.
- `responseFormat` (optional) controls the shape of the agent's output. Defaults to `{ type: 'text' }` for free-form text. Use `{ type: 'json', schema }` to force structured JSON output.
By default an agent returns free-form text. To get structured output, set `responseFormat` to `{ type: 'json' }` and provide a `schema`:
```ts src/agents/structured-agent.ts
import { defineAgent } from 'twenty-sdk/define';
export default defineAgent({
universalIdentifier: 'c4d5e6f7-a8b9-0123-cdef-456789012345',
name: 'lead-scorer',
label: 'Lead Scorer',
prompt: 'Score the lead and explain your reasoning.',
responseFormat: {
type: 'json',
schema: {
type: 'object',
properties: {
score: { type: 'number', description: 'Lead score from 0 to 100' },
summary: { type: 'string', description: 'Short reasoning for the score' },
},
required: ['score', 'summary'],
additionalProperties: false,
},
},
});
```
Schema notes:
- The schema is a flat object: each property's `type` must be a primitive (`string`, `number`, or `boolean`). Nested objects and arrays are not supported.
- `description` (optional) on each property guides the model on what to put there.
- `required` (optional) lists the properties the model must always return.
- `additionalProperties: false` (optional) forbids any property not declared in `properties`.
</Accordion>
<Accordion title="runAgent" description="Run an agent from a logic function">
`runAgent()` lets a logic function run one of your app's agents (with its
skills and tools). Identify the agent by the `universalIdentifier` you passed
to `defineAgent()`. Pass either a `prompt` string or a `messages` conversation
history — not both:
```ts src/logic-functions/run-enricher.ts
import { runAgent } from 'twenty-sdk/logic-function';
const { result, error, success } = await runAgent({
agentUniversalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123',
prompt: 'Enrich House Ad <recordId>: fill empty fields from its listing URL.',
});
```
For multi-turn bots (Slack, Discord, Teams, …), pass thread history as
`messages` instead of a single `prompt`:
```ts src/logic-functions/reply-in-thread.ts
import { runAgent } from 'twenty-sdk/logic-function';
const { result, error, success } = await runAgent({
agentUniversalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123',
messages: [
{ role: 'user', content: 'Who owns Acme?' },
{ role: 'assistant', content: 'Sarah owns the Acme account.' },
{ role: 'user', content: 'What was the last touchpoint?' },
],
});
```
Key points:
- Provide **exactly one** of `prompt` (string) or `messages` (1 to 100 entries
of `{ role: 'user' | 'assistant', content: string }`). A `user` message can
also carry file attachments — see below.
- The agent runs **synchronously** and can read/update records itself via its
own tools — `runAgent()` resolves once the run completes.
- An app can only run its own agents.
- The app's [default role](/developers/extend/apps/config/roles) must grant the
`AI` permission flag — add `SystemPermissionFlag.AI` to its
`permissionFlagUniversalIdentifiers` (or set `canAccessAllTools: true`).
Without it, `runAgent()` fails with a permission error.
- Set a generous `timeoutSeconds` on the logic function — agent runs can take
several seconds.
- `success` is `true` and `result` is non-null when the run completes; on
failure `success` is `false`, `result` is `null`, and `error` holds the
reason (for example, when the workspace ran out of AI credits mid-run).
```ts src/roles/default-role.ts
import { defineApplicationRole, SystemPermissionFlag } from 'twenty-sdk/define';
export default defineApplicationRole({
universalIdentifier: 'b648f87b-1d26-4961-b974-0908fd991061',
label: 'Default function role',
// runAgent() requires the AI permission flag on the app's default role.
permissionFlagUniversalIdentifiers: [SystemPermissionFlag.AI],
});
```
<Warning>
**Avoid loops:** if you call `runAgent()` from a `*.updated` database-event
trigger and the agent updates the same record, scope the trigger with
`updatedFields` to a field the agent never writes (e.g. the source URL), or
guard on whether any target field is still empty before calling `runAgent()`.
</Warning>
### Sending files with a message
A `user` message can carry `attachments`, so the agent sees a screenshot or a
PDF instead of only its name. Each entry references a file your app has already
uploaded into the `agent-chat` folder:
```ts src/logic-functions/describe-screenshot.ts
const { result } = await runAgent({
agentUniversalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123',
messages: [
{
role: 'user',
content: 'What does this error screen say?',
attachments: [{ fileId: uploadedFileId, filename: 'error.png' }],
},
],
});
```
Uploading requires the `UPLOAD_FILE` permission flag on the app's default role,
alongside `AI`. `filename` is optional and only labels the file for the model.
- Only `user` messages may carry attachments. They cannot be represented on an
assistant turn, so `runAgent()` rejects them there rather than dropping them.
- At most **10** attachments per message.
- The `fileId` must name a fully uploaded file in the same workspace, under the
`agent-chat` folder. Anything else fails the call instead of quietly
answering without the file.
### Running on behalf of a workspace member
Pass `runAsWorkspaceMemberId` when the run is triggered by a person — a chat
bot answering a message, for instance — so the agent acts as that member
instead of as the app:
```ts src/logic-functions/answer-question.ts
const { result } = await runAgent({
agentUniversalIdentifier: 'b3c4d5e6-f7a8-9012-bcde-f34567890123',
prompt: 'How many open opportunities do we have?',
runAsWorkspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7',
});
```
The run then acts with the **member's own role**: it can do whatever that
member can do and nothing more, records it creates are attributed to them, and
their row-level permissions apply. The agent's role does not participate — it
is the app's default for runs with nobody behind them. Omit the field for
autonomous runs (scheduled jobs, database-event triggers): those keep the
agent's own role. The acting application stays attached to the run's context
for provenance, without narrowing the member's permissions.
Your app is responsible for mapping the person who triggered the run to a
workspace member. Naming one requires an app access token, and what a token may
name depends on whether it carries a user:
- A token with **no user attached** may name any member. A logic function runs
with one, and so do tokens minted through `client_credentials` or from an API
key.
- A token issued **on behalf of a user**, as a front component receives, may
only name that user's own member.
Anything else — a plain user session, an API key without an app token — may not
name a member at all.
<Warning>
`runAgent()` throws when the workspace member cannot be resolved — an
unknown or removed member, or one with no role. It never falls back to the
agent's own role, since that would grant more access than the caller asked
for.
</Warning>
</Accordion>
</AccordionGroup>