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.swiftby adding aToolNameenum case and anAgentToolentry with a JSON schema. - Implement the logic in a
ToolExecutorextension file, accessing theeditorreference to manipulate theVideoProjecttimeline and clips. - Wire the tool into the dispatch loop in
ToolExecutor.swiftto make it reachable by the agent. - Invoke the tool via
AgentService.send()from Swift UI or through the MCP server endpoint at127.0.0.1:19789/mcpfor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →