How to Implement Custom Business Logic in Palmier Pro: A Step-by-Step Guide

You implement custom business logic in Palmier Pro by declaring a new tool in ToolDefinitions.swift, implementing the behavior in a ToolExecutor extension, and invoking it through the Agent service or MCP server.

Palmier Pro is an extensible video editing platform built around an AI Agent that communicates via declarative JSON tools. This guide shows you how to implement custom business logic in Palmier Pro by adding new tools to the agent's toolkit, allowing you to automate domain-specific workflows—from batch clip styling to automated analytics—without modifying the core editor UI.

Understanding the Tool Architecture

The Palmier Pro architecture separates tool declaration from execution, enabling safe extension through Swift extensions.

Tool Catalog

The Tool Catalog in Sources/PalmierPro/Agent/Tools/ToolDefinitions.swift (lines 4-38, 45-55) declares every available tool via the ToolName enum and AgentTool structs. Each entry defines the tool's name, description, and JSON schema for LLM validation.

Tool Executor

The Tool Executor layer maps tool names to Swift implementations. The core dispatcher lives in Sources/PalmierPro/Agent/Tools/ToolExecutor.swift, while specific tools are implemented in separate extension files like ToolExecutor+Timeline.swift. This executor receives a [String: Any] dictionary of arguments and returns a ToolResult (defined in ToolResult.swift).

Agent Service

The Agent Service (Sources/PalmierPro/Agent/AgentService.swift, lines 46-62, 84-100) manages chat sessions, streams LLM responses, and forwards tool uses to the executor. It operates on the Project Model (Sources/PalmierPro/Project/VideoProject.swift), which holds the timeline, tracks, and clips that your business logic will manipulate.

Declaring a Custom Tool

All custom logic must first be declared in the tool catalog so the LLM recognizes it as a valid operation.

Extending ToolDefinitions.swift

Open Sources/PalmierPro/Agent/Tools/ToolDefinitions.swift and add a new case to the ToolName enum:

case myCustomLogic = "my_custom_logic"

Next, append an AgentTool entry to the all array (around line 45-55) to describe the tool's interface:

AgentTool(
    name: .myCustomLogic,
    description: "Runs custom business logic supplied by the developer. The `payload` field receives arbitrary JSON that the executor can interpret.",
    inputSchema: objectSchema(
        properties: [
            "payload": ["type": "object", "description": "Arbitrary data for custom processing."]
        ],
        required: ["payload"]
    )
),

The objectSchema helper function constructs a JSON Schema dictionary that validates incoming tool arguments before execution.

Implementing the Business Logic

Once declared, you must implement the actual behavior in a ToolExecutor extension.

Creating the Executor Extension

Create a new file Sources/PalmierPro/Agent/Tools/ToolExecutor+MyCustomLogic.swift and implement the execution function:

import Foundation

extension ToolExecutor {
    /// Executes `my_custom_logic`.
    func myCustomLogic(args: [String: Any]) async -> ToolResult {
        // Extract payload – decode to a strongly-typed struct if preferred.
        guard let payload = args["payload"] as? [String: Any] else {
            return .error("Missing `payload`")
        }

        // Example business rule: flag clips longer than 5 seconds.
        if let minDuration = payload["minDurationFrames"] as? Int {
            await flagLongClips(minFrames: minDuration)
        }

        return .success(["text": "Business rule applied."])
    }

    private func flagLongClips(minFrames: Int) async {
        guard let editor = editor else { return }
        
        // Walk the timeline and manipulate clips via existing APIs.
        for track in editor.timeline.tracks {
            for clip in track.clips where clip.durationFrames > minFrames {
                await setClipProperties(
                    clipIds: [clip.id],
                    opacity: 0.5,
                    transform: ["borderWidth": 0.02]
                )
            }
        }
    }
}

This implementation follows the established pattern: validate arguments, access the editor reference to mutate the VideoProject model, and return a ToolResult.

Wiring Into the Dispatch Loop

The ToolExecutor.swift file contains a dispatch switch that routes tool names to their implementations. Add your case to the execute(name:args:) method:

case .myCustomLogic:
    return await myCustomLogic(args: args)

The agent's execution loop (runLoop in AgentService.swift) now automatically resolves calls to my_custom_logic, treating your custom code exactly like built-in operations.

Invoking Your Custom Logic

With the tool declared and implemented, you can trigger it from within the app or via external scripts.

From Swift Code

Invoke the tool through the AgentService from any Swift UI component:

Button("Run Business Rule") {
    Task {
        let args = ["payload": ["minDurationFrames": 150]]
        let json = try! JSONSerialization.data(withJSONObject: args)
        let text = "@my_custom_logic \(String(data: json, encoding: .utf8)!)"
        
        await agentService.send(text: text, mentions: [])
    }
}

The @my_custom_logic prefix signals the agent to generate a tool use. The JSON payload automatically passes through to your executor implementation.

Via the MCP Server

Palmier Pro exposes an HTTP endpoint at http://127.0.0.1:19789/mcp that accepts external tool calls. Trigger your logic from a CI job or external script:

curl -X POST http://127.0.0.1:19789/mcp \
  -H "Content-Type: application/json" \
  -d '{
        "messages": [
          {"role":"user","content":[{"type":"text","text":"@my_custom_logic"}]}
        ],
        "tools": [
          {"name":"my_custom_logic","input_schema":{"properties":{"payload":{"type":"object"}},"required":["payload"]}}
        ]
      }'

The server streams the response, executes your tool through the executor, and returns the result block to the caller.

Complete Working Example

Here is a full implementation that adds yellow highlight overlays to video clips when the payload contains highlight: true:

// ToolExecutor+Highlight.swift
import Foundation

extension ToolExecutor {
    func highlightVideos(args: [String: Any]) async -> ToolResult {
        guard let payload = args["payload"] as? [String: Any] else {
            return .error("Missing payload")
        }

        if let shouldHighlight = payload["highlight"] as? Bool, shouldHighlight {
            await addHighlightOverlayToAllVideoClips()
        }

        return .success(["text": "Highlight effect applied to video clips."])
    }

    private func addHighlightOverlayToAllVideoClips() async {
        guard let editor = editor else { return }
        
        for (trackIdx, track) in editor.timeline.tracks.enumerated() {
            for clip in track.clips where clip.mediaType == .video {
                await setClipProperties(
                    clipIds: [clip.id],
                    transform: ["opacity": 0.3, "color": "#FFFF0099"]
                )
            }
        }
    }
}

Add the corresponding case to the dispatch switch in ToolExecutor.swift:

case .highlightVideos:
    return await highlightVideos(args: args)

And declare it in ToolDefinitions.swift:

case highlightVideos = "highlight_videos"

Summary

  • Declare your custom tool in ToolDefinitions.swift by adding a ToolName enum case and an AgentTool entry with a JSON schema.
  • Implement the logic in a ToolExecutor extension file, accessing the editor reference to manipulate the VideoProject timeline and clips.
  • Wire the tool into the dispatch loop in ToolExecutor.swift to make it reachable by the agent.
  • Invoke the tool via AgentService.send() from Swift UI or through the MCP server endpoint at 127.0.0.1:19789/mcp for external automation.

This pattern ensures your custom business logic remains undoable, repeatable, and visible to the LLM for intelligent orchestration.

Frequently Asked Questions

Can I implement custom business logic without modifying the Palmier Pro source code?

Currently, you must add tool definitions and executor extensions within the Palmier Pro codebase in Sources/PalmierPro/Agent/Tools/. However, once implemented, you can trigger these tools from external scripts via the MCP server without recompiling the app. The architecture treats external MCP calls identically to internal agent requests.

What data types can I pass to my custom tool's payload?

The inputSchema in ToolDefinitions.swift supports standard JSON Schema types including string, integer, number, boolean, object, and array. The executor receives these as [String: Any] dictionaries. For complex business logic, decode the payload into a strongly-typed Swift struct using JSONDecoder for type safety.

How do I ensure my custom tool respects the project frame rate?

All timing values in Palmier Pro use project-frame units (the same FPS as the timeline). When processing durations or positions in your executor, reference clip.durationFrames and other timeline properties from VideoProject.swift rather than calculating raw time values. This ensures your business logic remains synchronized with the editor's playback settings.

Can my custom tool call other existing tools?

Yes. Your executor extension can invoke other tool methods directly, such as setClipProperties or timeline manipulation functions. This approach maintains consistency with the UI layer and ensures that all mutations flow through the same validation paths. The ToolExecutor class provides access to the full suite of existing editing operations.

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 →