> ## Documentation Index
> Fetch the complete documentation index at: https://hellotars.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Write a custom action

> Create a CodeKit toolkit, write and deploy a TypeScript action, and give it to an AI Agent gambit as a tool.

export const Browser = ({url, children}) => <div className="tars-browser">
    <div className="tars-browser-bar">
      <span className="tars-browser-dots">
        <i />
        <i />
        <i />
      </span>
      {url ? <span className="tars-browser-url">{url}</span> : null}
    </div>
    <div className="tars-browser-view">{children}</div>
  </div>;

export const img = {
  activity: {
    activityTimeline: "/docs/guides/activity/images/activity-timeline.png",
    capturedVariablesPanel: "/docs/guides/activity/images/captured-variables-panel.png",
    dataViewCards: "/docs/guides/activity/images/data-view-cards.png",
    exportDataDialog: "/docs/guides/activity/images/export-data-dialog.png",
    tasksPanelExportReady: "/docs/guides/activity/images/tasks-panel-export-ready.png"
  },
  analytics: {
    analyticsOverview: "/docs/guides/analytics/images/analytics-overview.png",
    exploreGallery: "/docs/guides/analytics/images/explore-gallery.png",
    exploreTab: "/docs/guides/analytics/images/explore-tab.png",
    goalForm: "/docs/guides/analytics/images/goal-form.png",
    goalsTab: "/docs/guides/analytics/images/goals-tab.png",
    improveDiagnostics: "/docs/guides/analytics/images/improve-diagnostics.png",
    improveTab: "/docs/guides/analytics/images/improve-tab.png",
    teamTab: "/docs/guides/analytics/images/team-tab.png"
  },
  billing: {
    billingOverview: "/docs/guides/billing/images/billing-overview.png",
    buyConversationsDialog: "/docs/guides/billing/images/buy-conversations-dialog.png",
    buySeatsDialog: "/docs/guides/billing/images/buy-seats-dialog.png",
    livechatSeatsTab: "/docs/guides/billing/images/livechat-seats-tab.png",
    organizationDeletionLifecycle: "/docs/guides/billing/images/organization-deletion-lifecycle.svg",
    organizationSwitcher: "/docs/guides/billing/images/organization-switcher.png",
    rolesAndAccessMatrix: "/docs/guides/billing/images/roles-and-access-matrix.png"
  },
  building: {
    agentBehaviorGuardrails: "/docs/guides/building/images/agent-behavior-guardrails.png",
    agentCardMenu: "/docs/guides/building/images/agent-card-menu.png",
    agentConfigure: "/docs/guides/building/images/agent-configure.png",
    agentExitRoutesDialog: "/docs/guides/building/images/agent-exit-routes-dialog.png",
    agentLibrary: "/docs/guides/building/images/agent-library.png",
    agentModelParameters: "/docs/guides/building/images/agent-model-parameters.png",
    aiAgentGambitLoop: "/docs/guides/building/images/ai-agent-gambit-loop.svg",
    builderBasics: "/docs/guides/building/images/builder-basics.png",
    builderConnectHandlePanel: "/docs/guides/building/images/builder-connect-handle-panel.png",
    builtInToolsDialog: "/docs/guides/building/images/built-in-tools-dialog.png",
    configureDebugTab: "/docs/guides/building/images/configure-debug-tab.png",
    configureLanguage: "/docs/guides/building/images/configure-language.png",
    configureNotifications: "/docs/guides/building/images/configure-notifications.png",
    createAgentDialog: "/docs/guides/building/images/create-agent-dialog.png",
    draftPublishRollbackPinning: "/docs/guides/building/images/draft-publish-rollback-pinning.svg",
    draftSwitcherChip: "/docs/guides/building/images/draft-switcher-chip.png",
    duplicateAgentDialog: "/docs/guides/building/images/duplicate-agent-dialog.png",
    flowConditions: "/docs/guides/building/images/flow-conditions.png",
    gambitPalette: "/docs/guides/building/images/gambit-palette.png",
    gambits: {
      gambitAiAgent: "/docs/guides/building/gambits/images/gambit-ai-agent.png",
      gambitApiCall: "/docs/guides/building/gambits/images/gambit-api-call.png",
      gambitAutoSuggestion: "/docs/guides/building/gambits/images/gambit-auto-suggestion.png",
      gambitButton: "/docs/guides/building/gambits/images/gambit-button.png",
      gambitCard: "/docs/guides/building/gambits/images/gambit-card.png",
      gambitChannel: "/docs/guides/building/gambits/images/gambit-channel.png",
      gambitCustomCode: "/docs/guides/building/gambits/images/gambit-custom-code.png",
      gambitDateTime: "/docs/guides/building/gambits/images/gambit-date-time.png",
      gambitGeoLocation: "/docs/guides/building/gambits/images/gambit-geo-location.png",
      gambitLanguage: "/docs/guides/building/gambits/images/gambit-language.png",
      gambitLiveChat: "/docs/guides/building/gambits/images/gambit-live-chat.png",
      gambitMediaUpload: "/docs/guides/building/gambits/images/gambit-media-upload.png",
      gambitNoInput: "/docs/guides/building/gambits/images/gambit-no-input.png",
      gambitRedirect: "/docs/guides/building/gambits/images/gambit-redirect.png",
      gambitStarRating: "/docs/guides/building/gambits/images/gambit-star-rating.png",
      gambitStartAndEnd: "/docs/guides/building/gambits/images/gambit-start-and-end.png",
      gambitText: "/docs/guides/building/gambits/images/gambit-text.png",
      gambitTrigger: "/docs/guides/building/gambits/images/gambit-trigger.png"
    },
    languagesTranslateGambit: "/docs/guides/building/images/languages-translate-gambit.png",
    languageSystemMessages: "/docs/guides/building/images/language-system-messages.png",
    newDraftDialog: "/docs/guides/building/images/new-draft-dialog.png",
    outputComponentsGrid: "/docs/guides/building/images/output-components-grid.png",
    publishDialog: "/docs/guides/building/images/publish-dialog.png",
    systemPromptDialog: "/docs/guides/building/images/system-prompt-dialog.png",
    testModeEventLog: "/docs/guides/building/images/test-mode-event-log.png",
    testModePanel: "/docs/guides/building/images/test-mode-panel.png",
    validationProblemsPanel: "/docs/guides/building/images/validation-problems-panel.png",
    variablePickerPanel: "/docs/guides/building/images/variable-picker-panel.png",
    variablePillInserted: "/docs/guides/building/images/variable-pill-inserted.png",
    versionsDraftsPanel: "/docs/guides/building/images/versions-drafts-panel.png"
  },
  campaigns: {
    campaignFlavorsTriggers: "/docs/guides/campaigns/images/campaign-flavors-triggers.svg",
    campaignReplyRouting: "/docs/guides/campaigns/images/campaign-reply-routing.svg",
    campaignSendAnatomy: "/docs/guides/campaigns/images/campaign-send-anatomy.svg",
    createACampaign: "/docs/guides/campaigns/images/create-a-campaign.png",
    walletReserveSettleRefund: "/docs/guides/campaigns/images/wallet-reserve-settle-refund.svg",
    webhookCampaignContractLocking: "/docs/guides/campaigns/images/webhook-campaign-contract-locking.svg",
    webhookCampaignLifecycleStates: "/docs/guides/campaigns/images/webhook-campaign-lifecycle-states.svg",
    webhookCampaignOneCallOneDelivery: "/docs/guides/campaigns/images/webhook-campaign-one-call-one-delivery.svg"
  },
  channels: {
    distributeWhatsappConnect: "/docs/guides/channels/images/distribute-whatsapp-connect.png",
    whatsappConnectDialogWaiting: "/docs/guides/channels/images/whatsapp-connect-dialog-waiting.png",
    whatsappEmptyState: "/docs/guides/channels/images/whatsapp-empty-state.png"
  },
  deploy: {
    bridgeEventGambitSection: "/docs/guides/deploy/images/bridge-event-gambit-section.png",
    deploymentSurfacesAndChannels: "/docs/guides/deploy/images/deployment-surfaces-and-channels.svg",
    designAgentTab: "/docs/guides/deploy/images/design-agent-tab.png",
    designLauncherTab: "/docs/guides/deploy/images/design-launcher-tab.png",
    embedIframeTab: "/docs/guides/deploy/images/embed-iframe-tab.png",
    embedTheWidget: "/docs/guides/deploy/images/embed-the-widget.png",
    mobileAppIntegration: "/docs/guides/deploy/images/mobile-app-integration.png",
    onYourDomain: "/docs/guides/deploy/images/on-your-domain.png",
    shareYourAgentLink: "/docs/guides/deploy/images/share-your-agent-link.png"
  },
  developer: {
    api: {
      channelSendTemplateVsMessage: "/docs/developer/api/images/channel-send-template-vs-message.svg",
      endUsers: {
        endUsersListCursorPagination: "/docs/developer/api/end-users/images/end-users-list-cursor-pagination.svg"
      },
      triggerCallConversationLifecycle: "/docs/developer/api/images/trigger-call-conversation-lifecycle.svg"
    },
    developerSurfacesMap: "/docs/developer/images/developer-surfaces-map.svg",
    embed: {
      embedConfigToWidgetContext: "/docs/developer/embed/images/embed-config-to-widget-context.svg",
      hostPageBridgeFlow: "/docs/developer/embed/images/host-page-bridge-flow.svg",
      identitySigningFlow: "/docs/developer/embed/images/identity-signing-flow.svg",
      scriptVsIframeEmbed: "/docs/developer/embed/images/script-vs-iframe-embed.svg"
    },
    mobile: {
      bridgeMessageProtocol: "/docs/developer/mobile/images/bridge-message-protocol.svg",
      webviewBridgeArchitecture: "/docs/developer/mobile/images/webview-bridge-architecture.svg"
    }
  },
  endUsers: {
    anonymousToIdentifiedProfile: "/docs/guides/end-users/images/anonymous-to-identified-profile.svg",
    bulkActionBar: "/docs/guides/end-users/images/bulk-action-bar.png",
    campaignConsentGate: "/docs/guides/end-users/images/campaign-consent-gate.svg",
    consentPerChannelStates: "/docs/guides/end-users/images/consent-per-channel-states.svg",
    dynamicVsStaticSegments: "/docs/guides/end-users/images/dynamic-vs-static-segments.svg",
    endUserDirectory: "/docs/guides/end-users/images/end-user-directory.png",
    endUserProfileAcrossChannels: "/docs/guides/end-users/images/end-user-profile-across-channels.svg",
    endUserProfilePanes: "/docs/guides/end-users/images/end-user-profile-panes.png",
    profileMergeAndTakeoverProtection: "/docs/guides/end-users/images/profile-merge-and-takeover-protection.svg",
    segmentBuilder: "/docs/guides/end-users/images/segment-builder.png",
    segmentsPage: "/docs/guides/end-users/images/segments-page.png"
  },
  getStarted: {
    agentStates: "/docs/get-started/images/agent-states.svg",
    buildToLivePath: "/docs/get-started/images/build-to-live-path.svg",
    dashboardSidebarMap: "/docs/get-started/images/dashboard-sidebar-map.png",
    organizationOwnershipAndRoles: "/docs/get-started/images/organization-ownership-and-roles.svg",
    quickstart: {
      step1CreateYourAccount: "/docs/get-started/images/quickstart/step-1-create-your-account.png",
      step3ConnectTheAiAgent: "/docs/get-started/images/quickstart/step-3-connect-the-ai-agent.png",
      step4TestMode: "/docs/get-started/images/quickstart/step-4-test-mode.png"
    },
    quickstartCanvas: "/docs/get-started/images/quickstart-canvas.png"
  },
  knowledge: {
    addASource: "/docs/guides/knowledge/images/add-a-source.png",
    addRetrieverPicker: "/docs/guides/knowledge/images/add-retriever-picker.png",
    attachKnowledgeGambit: "/docs/guides/knowledge/images/attach-knowledge-gambit.png",
    configureARetriever: "/docs/guides/knowledge/images/configure-a-retriever.png",
    createKnowledgeBaseDialog: "/docs/guides/knowledge/images/create-knowledge-base-dialog.png",
    customRetrieverDialog: "/docs/guides/knowledge/images/custom-retriever-dialog.png",
    datasetTab: "/docs/guides/knowledge/images/dataset-tab.png",
    documentChangeReindexLifecycle: "/docs/guides/knowledge/images/document-change-reindex-lifecycle.svg",
    documentsTabPreview: "/docs/guides/knowledge/images/documents-tab-preview.png",
    importAWebsite: "/docs/guides/knowledge/images/import-a-website.png",
    kbList: "/docs/guides/knowledge/images/kb-list.png",
    retrieverIndexStates: "/docs/guides/knowledge/images/retriever-index-states.svg",
    retrieverQueryPipeline: "/docs/guides/knowledge/images/retriever-query-pipeline.svg",
    standardVsParentChildChunking: "/docs/guides/knowledge/images/standard-vs-parent-child-chunking.svg",
    syncManagement: "/docs/guides/knowledge/images/sync-management.png",
    testRetriever: "/docs/guides/knowledge/images/test-retriever.png"
  },
  liveChat: {
    agentWorkspace: "/docs/guides/live-chat/images/agent-workspace.png",
    assignedAgentPicker: "/docs/guides/live-chat/images/assigned-agent-picker.png",
    automaticAssignmentRouting: "/docs/guides/live-chat/images/automatic-assignment-routing.svg",
    availabilityDropdown: "/docs/guides/live-chat/images/availability-dropdown.png",
    cannedResponses: "/docs/guides/live-chat/images/canned-responses.png",
    composerPreferences: "/docs/guides/live-chat/images/composer-preferences.png",
    copilotPanel: "/docs/guides/live-chat/images/copilot-panel.png",
    filterConversationsDialog: "/docs/guides/live-chat/images/filter-conversations-dialog.png",
    inboxAnalyticsTab: "/docs/guides/live-chat/images/inbox-analytics-tab.png",
    inboxAvailabilityTab: "/docs/guides/live-chat/images/inbox-availability-tab.png",
    inboxesSettings: "/docs/guides/live-chat/images/inboxes-settings.png",
    inboxRoutingTab: "/docs/guides/live-chat/images/inbox-routing-tab.png",
    labelsSettings: "/docs/guides/live-chat/images/labels-settings.png",
    newAttributeDialog: "/docs/guides/live-chat/images/new-attribute-dialog.png",
    newCannedResponseDialog: "/docs/guides/live-chat/images/new-canned-response-dialog.png",
    notificationPreferences: "/docs/guides/live-chat/images/notification-preferences.png",
    notificationsPage: "/docs/guides/live-chat/images/notifications-page.png",
    pickupTargetCard: "/docs/guides/live-chat/images/pickup-target-card.png",
    resolveMenu: "/docs/guides/live-chat/images/resolve-menu.png",
    searchDialog: "/docs/guides/live-chat/images/search-dialog.png",
    seatGateConversation: "/docs/guides/live-chat/images/seat-gate-conversation.png",
    seatsSettings: "/docs/guides/live-chat/images/seats-settings.png",
    workTheInbox: "/docs/guides/live-chat/images/work-the-inbox.png"
  },
  security: {
    addOpenRouterKeyDialog: "/docs/guides/security/images/add-open-router-key-dialog.png",
    auditChainVerified: "/docs/guides/security/images/audit-chain-verified.png",
    auditEventDrawer: "/docs/guides/security/images/audit-event-drawer.png",
    auditExportDialog: "/docs/guides/security/images/audit-export-dialog.png",
    auditLogViewer: "/docs/guides/security/images/audit-log-viewer.png",
    auditRecordLifecycle: "/docs/guides/security/images/audit-record-lifecycle.svg",
    logPrivacyRedactionEnabled: "/docs/guides/security/images/log-privacy-redaction-enabled.png",
    modelProviderEmptyState: "/docs/guides/security/images/model-provider-empty-state.png",
    privacySettings: "/docs/guides/security/images/privacy-settings.png",
    siemConnectWizard: "/docs/guides/security/images/siem-connect-wizard.png",
    supportActorMasking: "/docs/guides/security/images/support-actor-masking.svg"
  },
  tools: {
    addToolModal: "/docs/guides/tools/images/add-tool-modal.png",
    addToolsPanel: "/docs/guides/tools/images/add-tools-panel.png",
    codekitActionEditor: "/docs/guides/tools/images/codekit-action-editor.png",
    codekitConnectToolkitsDialog: "/docs/guides/tools/images/codekit-connect-toolkits-dialog.png",
    codekitSchemaDialog: "/docs/guides/tools/images/codekit-schema-dialog.png",
    codekitToolkit: "/docs/guides/tools/images/codekit-toolkit.png",
    codekitToolkitAnatomy: "/docs/guides/tools/images/codekit-toolkit-anatomy.svg",
    createActionDialog: "/docs/guides/tools/images/create-action-dialog.png",
    createCodekitDialog: "/docs/guides/tools/images/create-codekit-dialog.png",
    responseFilteringDialog: "/docs/guides/tools/images/response-filtering-dialog.png",
    toolGambitEditorAgentMode: "/docs/guides/tools/images/tool-gambit-editor-agent-mode.png",
    toolGambitEditorWorkflowMode: "/docs/guides/tools/images/tool-gambit-editor-workflow-mode.png",
    toolkitDetail: "/docs/guides/tools/images/toolkit-detail.png",
    toolsConnected: "/docs/guides/tools/images/tools-connected.png",
    toolsDirectory: "/docs/guides/tools/images/tools-directory.png",
    viewActionsModal: "/docs/guides/tools/images/view-actions-modal.png",
    workflowModeResponse: "/docs/guides/tools/images/workflow-mode-response.png"
  }
};

export const urls = {
  app: "https://dashboard.hellotars.com",
  site: "https://hellotars.com",
  support: "mailto:support@hellotars.com",
  status: "https://status.hellotars.com"
};

At the end of this page, you have a deployed custom action, and an AI Agent gambit in your flow can call it. The example builds an **Order Lookup** toolkit with one action, **Get order status**, that returns the status of an order number.

## Before you start

Before you start, sign in to <a href={urls.app}>the dashboard</a> and select the organization you want to work in.

* You need a role that can manage resources, such as Admin or Member.
* If the action will call a third-party toolkit such as Gmail, connect it first. See [Connect a toolkit](/docs/guides/tools/connect-a-toolkit).
* For what a CodeKit toolkit is and how secrets and deployment fit together, read [CodeKit overview](/docs/guides/tools/codekit-overview).

## Create a CodeKit toolkit

<Steps>
  <Step title="Open Tools and select Create Tool">
    The **Create Custom Tool** dialog opens.
  </Step>

  <Step title="Enter a Name, such as Order Lookup">
    Letters, numbers, spaces, underscores, and parentheses are allowed. The name cannot start with a number.
  </Step>

  <Step title="Optional: fill in Description, then select Create Tool">
    A **"Order Lookup" created** message confirms, and the toolkit page opens.
  </Step>
</Steps>

The new toolkit holds one **Greeting** action as a starting point. It is switched off and not deployed. Edit it into your first action, or delete it.

<Frame>
  <img src={img.tools.createCodekitDialog} alt="The Create Custom Tool dialog with the Name field set to Order Lookup and a short description filled in" />
</Frame>

## The toolkit page

The toolkit page holds everything its actions share. **Save** stores the toolkit name, description, and logo.

<Browser url={urls.app}>
  <img src={img.tools.codekitToolkit} alt="The Order Lookup toolkit page with the Toolkit Info card, the empty Environment Variables card with its Add Secret button, the Log privacy card, and an Actions list holding the deployed Get Order Status action with its enable switch on and its Duplicate and delete buttons" />
</Browser>

| Card                      | Controls                                                                                                                                                                                                                                                                      |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Toolkit Info**          | The **Logo** dropzone, **Drop logo or click**, takes an image file. The field below it takes a pasted logo URL instead. **Name** and **Description** edit the toolkit. The **Name** field warns **A toolkit with this name already exists** if another toolkit has that name. |
| **Environment Variables** | Secrets shared by every action in the toolkit. **Add Secret** creates one. See [Add secrets](#add-secrets).                                                                                                                                                                   |
| **Log privacy**           | The **Enable redaction** switch reveals **Redact inputs** and **Redact outputs**. Redacted values never reach execution logs in plain text.                                                                                                                                   |
| **Used by**               | The agents that currently use this toolkit in their flow. Check it before you delete anything.                                                                                                                                                                                |
| **Delete Toolkit**        | Shown to organization admins only. Its **Delete** button removes the toolkit and all of its actions after a confirmation. Agents that use them stop working, and the deletion cannot be undone.                                                                               |

### The Actions list

The **Actions** heading shows the count of actions. **Search actions** filters the list, and **Add Action** creates a new one. An empty toolkit shows **No actions yet** with a **Create your first action** button.

Each row shows the action's name, a deploy dot, its description, an enable switch, a **Duplicate action** button, a trash icon, and a chevron. Select the row to open the editor.

| Deploy dot | Meaning                           |
| ---------- | --------------------------------- |
| Green      | The action is deployed.           |
| Spinner    | A build or deployment is running. |
| Red alert  | The last deployment failed.       |

An action that was never deployed shows no dot.

Every new action starts with its enable switch off. The switch stays disabled until the action is deployed, and its tooltip reads **Deploy this action before enabling it**. A change to the switch applies at once, without **Save**. The toolkit card on the **Connected** tab of the **Tools** page counts enabled actions.

### Duplicate an action or a toolkit

**Duplicate action** on a row adds a copy of the action to the same toolkit. A **"{"{name}"}" duplicated** message confirms.

**Duplicate toolkit** is the copy icon on a CodeKit toolkit card on the **Connected** tab. It copies the toolkit with its actions, toolkit secrets, logo, and log privacy settings, then opens the copy.

Every copied action starts switched off and not deployed. Deploy each copy before you turn it on.

## Create an action

<Steps>
  <Step title="Select Add Action">
    The **Create Action** dialog opens.
  </Step>

  <Step title="Enter a Name, such as Get order status">
    A **Slug** preview updates below the field as you type, for example `get_order_status`.
  </Step>

  <Step title="Enter a Description, then select Create Action">
    **Description** is required. It tells the model what the action does. The action editor opens.
  </Step>
</Steps>

<Frame>
  <img src={img.tools.createActionDialog} alt="The Create Action dialog with the Name field set to Get order status and its description filled in" />
</Frame>

## The action editor

The editor page has a **Configure** rail on the left and the code editor on the right.

<Browser url={urls.app}>
  <img src={img.tools.codekitActionEditor} alt="The action editor for Get order status with the Deployed badge, the Details, Input / Output Schema, Connected Toolkits, and Secrets cards on the left, and the TypeScript editor on the right" />
</Browser>

### Header

The back link carries the toolkit name, and the heading shows the action name. A badge tracks the deployment: **Draft**, **Building**, **Deploying**, **Built**, **Deployed**, or **Error**. After a build, the bundle size and the last build time appear beside the badge.

| Button               | What it does                                                                                                                                                          |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Reset code**       | Replaces the editor content with the starter template. A confirmation dialog appears first. Nothing is stored until you save.                                         |
| **Reset everything** | Deletes every action-level secret at once, and resets the code and schema in the editor. A confirmation dialog appears first. Toolkit-level secrets are not affected. |
| **Save**             | Stores the code, schema, details, and connected toolkits. An **Action saved** message confirms.                                                                       |
| **Deploy**           | Saves, then builds and releases the action. A **Deployment started** message confirms.                                                                                |

<Warning>
  **Reset everything** deletes action-level secrets immediately, before you save. Neither reset button rolls back to the deployed version.
</Warning>

### Details

| Field            | What it holds                                                                        |
| ---------------- | ------------------------------------------------------------------------------------ |
| **Display name** | The name shown in the toolkit and in the builder.                                    |
| **Action ID**    | The slug your code and the model use. It follows the name until you edit it by hand. |
| **Description**  | What the action does. The model reads it to decide when to call the action.          |

### Define inputs and outputs

The **Input / Output Schema** card shows one chip per property. Hover a chip to see its type, whether it is required, and how to read it in code, such as `input.order_number`. Select **Edit schema** to open the **Input / Output** dialog.

<Frame>
  <img src={img.tools.codekitSchemaDialog} alt="The Input / Output dialog on the Inputs tab in the Form view, with the Form and JSON buttons, a saved order_number row showing its string type, Required badge, description, and Edit, Duplicate, and Delete buttons, and Add parameter below it" />
</Frame>

Each tab has **Form** and **JSON** buttons that switch between a structured form and the raw JSON Schema. Both views edit the same schema. **Form** is disabled while the JSON has errors or a field has no `type`.

On the **Inputs** tab in the form, **Add parameter** adds a row. Each row takes a parameter name, a type, and a description, plus a **Required** switch. The types are `string`, `number`, `integer`, `boolean`, `array`, `object`, and `null`. An `object` parameter takes its own list of properties, and an `array` parameter takes an item type.

The description is required so the model understands the parameter. Confirm the row with the check button, then select **Done**. A saved row offers **Edit**, **Duplicate**, and **Delete**. The form shows up to five levels of nesting, and deeper levels are kept for the JSON view.

In the JSON view, **Supported schema** opens a guide to the accepted types and keywords, with an example. Its copy button copies the guide as Markdown. Anything outside the supported subset is flagged. The maximize button opens the editor in the **Edit schema JSON** dialog.

On the **Outputs** tab, `successful` and `error` are fixed. You pick the type of `data` and describe what the action returns. That description is required too. An `object` type lets you list the properties of `data`, so later steps can reference them by name.

For the example, add one required `string` parameter named `order_number` with the description "The order number the customer received in their confirmation email, for example ORD-1042".

### Add secrets

API keys and tokens belong in secrets, not in code. Your code reads a secret as `env.KEY`.

| Where you add it                                                   | Scope                                                      | Badge in the editor     |
| ------------------------------------------------------------------ | ---------------------------------------------------------- | ----------------------- |
| **Add** on the **Secrets** card in the action editor               | This action only. The dialog is **Add action secret**.     | **A**, **Action-only**  |
| **Add Secret** under **Environment Variables** on the toolkit page | Every action in the toolkit. The dialog is **Add Secret**. | **T**, **Toolkit-wide** |

Both dialogs take a **Key**, a masked **Value**, and an optional **Description**. As you type, the key is converted to uppercase and spaces become underscores. It must start with a letter and can contain only uppercase letters, numbers, and underscores, such as `IP_API_KEY`.

**Save secret** stores it. Values are encrypted at rest.

Select any secret chip to open the **Secrets** dialog, which lists both scopes with a **Toolkit** or **Action** badge and a delete button. Deleting a toolkit secret removes it from every action.

### Connect toolkits

A custom action can call the toolkits your organization has already connected. Select **Connect** on the **Connected Toolkits** card to open the **Connect Toolkits** dialog.

<Frame>
  <img src={img.tools.codekitConnectToolkitsDialog} alt="The Connect Toolkits dialog with a searchable toolkit list on the left and the selected toolkit's actions with checkboxes on the right, plus Save Selection and Done buttons" />
</Frame>

Pick a toolkit on the left. Toolkits that are not connected are dimmed. On the right, check the actions your code needs, or use **Select all** and **Clear**.

Select **Done** to keep the selection, then save the action. To pick actions from several toolkits, select **Save Selection** before you switch to the next toolkit.

Each connected toolkit appears as a chip with a connection dot and the count of selected actions. Its tooltip shows the call shape. For a Gmail toolkit the namespace is `toolkits.gmail`, and the **Types** dialog shows the exact input and output of each action.

### Write the code

The tab above the editor is named after the **Action ID**, such as `get_order_status.ts`.

| Control      | What it does                                                                                          |
| ------------ | ----------------------------------------------------------------------------------------------------- |
| Panel toggle | Collapses the **Configure** rail so the editor fills the page.                                        |
| Error count  | Appears as a red pill, such as **2 errors**, while the code has TypeScript errors.                    |
| **Beautify** | Formats the code. The shortcut is Shift+Alt+F.                                                        |
| **Types**    | Opens the **Available types** dialog with the definitions behind IntelliSense, and a **Copy** button. |

The starter code imports from `tars` and defines one `run` function that returns `successful`, `data`, and `error`. Your job is the body of `run`. Cmd+S on Mac or Ctrl+S on Windows saves from inside the editor.

The `tars` module gives you:

| Import               | What it does                                                                                                                                                                                                                           |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Input` and `Output` | Types generated from your schema, so the editor suggests `input.order_number` as you type.                                                                                                                                             |
| `env`                | Your secrets, as `env.IP_API_KEY`.                                                                                                                                                                                                     |
| `http`               | A client with `get`, `post`, `put`, `patch`, `delete`, and `request`. A failed request returns `success`, `status`, `data`, `error`, and `headers` instead of throwing. An invalid address or a private host throws.                   |
| `toolkits`           | The connected toolkits you selected, as `toolkits.gmail`.                                                                                                                                                                              |
| `storage`            | A key-value store shared by all custom actions in your organization, as `storage.main.get`, `storage.main.put`, `storage.main.delete`, and `storage.main.list`. `get`, `put`, and `delete` also take batch forms, capped at 128 items. |

The example action:

```ts theme={null}
import { type Input, type Output } from 'tars'

const ORDERS: Record<string, { status: string; carrier: string; eta: string }> = {
  'ORD-1042': { status: 'Shipped', carrier: 'DHL Express', eta: '2026-08-18' },
  'ORD-1043': { status: 'Processing', carrier: 'Not assigned yet', eta: '2026-08-21' },
  'ORD-1044': { status: 'Delivered', carrier: 'FedEx', eta: '2026-08-12' },
}

async function run(input: Input): Promise<Output> {
  const orderNumber = input.order_number?.trim().toUpperCase()
  if (!orderNumber) {
    return { successful: false, data: null, error: 'order_number is required.' }
  }

  const order = ORDERS[orderNumber]
  if (!order) {
    return { successful: false, data: null, error: `No order found for ${orderNumber}.` }
  }

  return {
    successful: true,
    error: null,
    data: {
      orderNumber,
      status: order.status,
      carrier: order.carrier,
      estimatedDelivery: order.eta,
      summary: `${orderNumber} is ${order.status.toLowerCase()} via ${order.carrier}, expected ${order.eta}.`,
    },
  }
}
```

Return `successful: false` with a clear `error` for every failure you can foresee. The AI Agent gambit reads that message and can tell the end user what went wrong.

## Save and deploy

<Warning>
  Deploying releases the action to your whole organization. While the deployment runs, and after a failed deployment, agent calls to the action fail.
</Warning>

<Steps>
  <Step title="Select Deploy">
    The action is saved first. The badge moves through **Building** and **Deploying**.
  </Step>

  <Step title="Wait for the Deployed badge">
    The bundle size and build time appear beside it. The row on the toolkit page shows a green dot.
  </Step>

  <Step title="Turn on the action's switch on the toolkit page">
    The switch is available only after the first deployment.
  </Step>
</Steps>

**Save** and **Deploy** stay disabled while a blocker exists, and the tooltip names it. The last two blockers apply to **Deploy** only.

| Tooltip                                                                     | Cause                                                      |
| --------------------------------------------------------------------------- | ---------------------------------------------------------- |
| **Action name is required**                                                 | **Display name** is empty.                                 |
| **Name cannot start with a number**                                         | **Display name** starts with a digit.                      |
| **Only letters, numbers, spaces, underscores, and parentheses are allowed** | **Display name** holds another character.                  |
| **Action slug is required**                                                 | The **Action ID** field was cleared.                       |
| **Action description is required**                                          | **Description** is empty.                                  |
| **Code cannot be empty**                                                    | The editor holds no code.                                  |
| **Fix 2 errors in the code**                                                | The editor reports TypeScript errors, with the live count. |
| **A deployment is already in progress**                                     | A deployment for this action is still running.             |
| **Building…**                                                               | A build is still running.                                  |

If the badge ends in **Error**, the failure message appears in a red banner under the header. Fix the code and select **Deploy** again. Agents cannot call the action until a deployment succeeds.

## Use the action from an agent

<Steps>
  <Step title="Open your agent's Make page and select the Tools tab in the gambit palette">
    **CodeKit** is listed under **Connected**.
  </Step>

  <Step title="Drag CodeKit onto the builder">
    A tool gambit appears and its editor opens.
  </Step>

  <Step title="Under Tool Selection, pick your toolkit in the CodeKit dropdown">
    The gambit takes the toolkit's name and logo. **Configure** opens the toolkit page in a new browser tab.
  </Step>

  <Step title="Under Sub-tools, turn on the actions this gambit offers">
    The section badge counts the included actions.
  </Step>

  <Step title="Connect the tool gambit to an AI Agent gambit">
    Drag from the tool gambit's handle to the tool handle on the left of the AI Agent gambit.
  </Step>
</Steps>

<Frame>
  <img src={img.tools.toolGambitEditorAgentMode} alt="The tool gambit editor for a CodeKit tool gambit, with the Order Lookup toolkit picked in the CodeKit dropdown under Tool Selection and the Sub-tools section showing Get order status turned on" />
</Frame>

The tool gambit shows as a badge in the agent's **Tools & Capabilities** region with a **Ready** dot. For the other ways to wire a tool gambit to an AI Agent gambit, see [Enable and use actions](/docs/guides/tools/enable-and-use-actions).

To run the action as a fixed flow step instead, turn on **Workflow Mode** in the tool gambit editor. Workflow Mode needs the `object` type for `data` in the output schema, with at least one property. See [Workflow Mode](/docs/guides/tools/enable-and-use-actions#workflow-mode).

## Verify

Select **Test** in the builder toolbar and ask something that needs the action, such as "Where is my order ORD-1042?". The agent calls **Get order status** and replies with the status, carrier, and expected delivery date.

## Related pages

* [CodeKit overview](/docs/guides/tools/codekit-overview)
* [Enable and use actions](/docs/guides/tools/enable-and-use-actions)
* [AI Agent gambit](/docs/guides/building/gambits/ai-agent)
* [Testing your agent](/docs/guides/building/testing-your-agent)
