diff --git a/dynamixs-ai-app/src/main/java/ai/dynamixs/AIAgentController.java b/dynamixs-ai-app/src/main/java/ai/dynamixs/AIAgentController.java
new file mode 100644
index 0000000..08fb5ff
--- /dev/null
+++ b/dynamixs-ai-app/src/main/java/ai/dynamixs/AIAgentController.java
@@ -0,0 +1,140 @@
+package ai.dynamixs;
+
+import java.io.Serializable;
+import java.util.logging.Logger;
+
+import org.imixs.ai.ImixsAIContextHandler;
+import org.imixs.ai.workflow.ImixsAIToolCallEvent;
+import org.imixs.ai.workflow.OpenAIAPIService;
+import org.imixs.workflow.exceptions.ModelException;
+import org.imixs.workflow.exceptions.PluginException;
+import org.imixs.workflow.faces.data.WorkflowController;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.Conversation;
+import jakarta.enterprise.context.ConversationScoped;
+import jakarta.enterprise.event.Observes;
+import jakarta.enterprise.event.Reception;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+
+@Named("aiAgent")
+@ConversationScoped
+public class AIAgentController implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ private static Logger logger = Logger.getLogger(AIAgentController.class.getName());
+
+ @Inject
+ private Conversation conversation;
+
+ @Inject
+ private OpenAIAPIService openAIAPIService;
+
+ @Inject
+ private ImixsAIContextHandler contextHandler;
+
+ @Inject
+ protected WorkflowController workflowController;
+
+ private String userMessage;
+
+ private String agentResponse;
+ private String redirectUrl;
+
+ private static final int MAX_ITERATIONS = 10; // prevent infinite loops
+
+ /**
+ * Starts the conversation scope and initializes the context.
+ */
+ @PostConstruct
+ public void init(String systemPrompt) {
+ if (conversation.isTransient()) {
+ conversation.begin();
+ }
+
+ try {
+ contextHandler.init();
+ contextHandler.addSystemMessage(systemPrompt);
+ } catch (PluginException e) {
+ logger.warning("Failed to init AIAgent: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Main entry point for the frontend. Runs the agent loop and returns either a
+ * text response or a redirect URL.
+ */
+ public String chat() throws PluginException {
+ redirectUrl = null;
+ agentResponse = null;
+
+ logger.info("├── Starting AI Agent");
+ logger.info("│ ├── User Question: " + userMessage);
+ contextHandler.addQuestion(userMessage, null, null);
+
+ // Agent Loop
+ int iterations = 0;
+ while (iterations < MAX_ITERATIONS) {
+ iterations++;
+
+ String response = openAIAPIService.postPromptCompletion(
+ contextHandler, null, true);
+
+ // Tool call? → processToolCallResult handles CDI events
+ boolean wasToolCall = openAIAPIService.processToolCallResult(
+ response, contextHandler);
+
+ if (!wasToolCall) {
+ // Normal text response → done
+ agentResponse = openAIAPIService.processPromptResult(
+ response, null, null);
+ return null; // stay on current page
+ }
+
+ // Check if a tool call produced a redirect URL
+ if (redirectUrl != null) {
+ conversation.end();
+ return redirectUrl; // JSF redirect
+ }
+ }
+
+ throw new PluginException(AIAgentController.class.getSimpleName(),
+ "AGENT_LOOP_ERROR", "Max iterations reached!");
+ }
+
+ public void onToolCall(@Observes(notifyObserver = Reception.IF_EXISTS) ImixsAIToolCallEvent event)
+ throws PluginException {
+ try {
+ if ("start_process".equals(event.getToolName())) {
+ String modelVersion = event.getArguments().getString("$modelversion");
+ int taskId = event.getArguments().getInt("$taskid");
+ String processRef = event.getArguments().getString("process.ref");
+
+ workflowController.create(modelVersion, taskId, processRef);
+
+ String url = "/pages/workitems/workitem/" + "";
+ // event.setRedirectUrl(url);
+ event.setResult("Process started: " + taskId);
+ }
+ } catch (ModelException e) {
+ throw new PluginException(e.getErrorContext(), e.getErrorCode(), e.getMessage());
+ }
+ }
+
+ public void setRedirectUrl(String url) {
+ this.redirectUrl = url;
+ }
+
+ public String getUserMessage() {
+ return userMessage;
+ }
+
+ public void setUserMessage(String userMessage) {
+ this.userMessage = userMessage;
+ }
+
+ public String getAgentResponse() {
+ return agentResponse;
+ }
+}
\ No newline at end of file
diff --git a/dynamixs-ai-app/src/main/webapp/layout/servicenav_custom.xhtml b/dynamixs-ai-app/src/main/webapp/layout/servicenav_custom.xhtml
new file mode 100644
index 0000000..9b8548e
--- /dev/null
+++ b/dynamixs-ai-app/src/main/webapp/layout/servicenav_custom.xhtml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dynamixs-ai-app/src/test/java/ai/dynamixs/agent/TestAgentCall.java b/dynamixs-ai-app/src/test/java/ai/dynamixs/agent/TestAgentCall.java
new file mode 100644
index 0000000..4d9ffa1
--- /dev/null
+++ b/dynamixs-ai-app/src/test/java/ai/dynamixs/agent/TestAgentCall.java
@@ -0,0 +1,140 @@
+/****************************************************************************
+ * Copyright (c) 2022-2025 Imixs Software Solutions GmbH and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0
+ *
+ * This Source Code may also be made available under the terms of the
+ * GNU General Public License, version 2 or later (GPL-2.0-or-later),
+ * which is available at https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
+ ****************************************************************************/
+
+package ai.dynamixs.agent;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Field;
+import java.util.Date;
+import java.util.Optional;
+
+import org.imixs.ai.ImixsAIContextHandler;
+import org.imixs.ai.workflow.OpenAIAPIConnector;
+import org.imixs.ai.workflow.OpenAIAPIService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+/**
+ * Test class to test a function call
+ *
+ *
+ * @author rsoika
+ */
+@ExtendWith(MockitoExtension.class)
+public class TestAgentCall {
+
+ private static final String MISTRAL_ENDPOINT = "https://api.llama.cpp.imixs.com/";
+ public static final String MISTRAL_API_KEY = "7fdb663fa87ea16731c1e0ece8ad3214e358423eabf2403188110c731ce07321";
+
+ private OpenAIAPIService service;
+ private OpenAIAPIConnector connector;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ connector = new OpenAIAPIConnector();
+ connector.setServiceEndpointApiKey(Optional.of(MISTRAL_API_KEY));
+ connector.setServiceEndpointUser(Optional.empty());
+ connector.setServiceEndpointPassword(Optional.empty());
+ injectField(connector, "serviceTimeout", 30000);
+ injectField(connector, "serviceEndpoint", Optional.of(MISTRAL_ENDPOINT));
+
+ service = new OpenAIAPIService();
+ injectField(service, "openAIAPIConnector", connector);
+ }
+
+ @Test
+ void testHelloWorld() throws Exception {
+ // Build a simple "Hello World" prompt
+ ImixsAIContextHandler contextHandler = new ImixsAIContextHandler();
+ contextHandler.addQuestion("Say hello world!", null, null);
+
+ String response = service.postPromptCompletion(contextHandler, MISTRAL_ENDPOINT, true);
+
+ assertNotNull(response);
+ System.out.println("Response: " + response);
+ }
+
+ @Test
+ void testFunctionCalling() throws Exception {
+ ImixsAIContextHandler contextHandler = new ImixsAIContextHandler();
+
+ // Define system context
+ contextHandler.addSystemMessage("""
+ You are a workflow assistant integrated into the Imixs-Office-Workflow suite.
+ The following BPMN processes are available:
+ - urlaubsantrag: Vacation request process
+ - reisekostenabrechnung: Travel expense process
+ - eingangsrechnung: Incoming invoice process
+
+ When a user wants to start a process, always call the load_skill tool
+ with the matching process_id. Never ask the user for the process ID.
+ """);
+
+ // Define the load_skill function
+ contextHandler.addFunction(
+ "load_skill",
+ "Loads details about an available BPMN workflow process",
+ """
+ {
+ "type": "object",
+ "properties": {
+ "process_id": {
+ "type": "string",
+ "description": "The ID of the BPMN process, e.g. urlaubsantrag"
+ }
+ },
+ "required": ["process_id"]
+ }
+ """);
+
+ contextHandler.addQuestion(
+ "I need next week off. Can you start the vacation request process?",
+ "tom.clancy", new Date());
+
+ // Print the full request JSON to verify it looks correct
+ System.out.println("Request: " + contextHandler.toString());
+
+ String response = service.postPromptCompletion(contextHandler, MISTRAL_ENDPOINT, true);
+ assertNotNull(response);
+ System.out.println("Response: " + response);
+
+ // Check if Mistral responded with a tool call
+ assertTrue(response.contains("load_skill"),
+ "Expected Mistral to call load_skill tool");
+
+ // Verify Mistral responded with a tool call
+ assertTrue(response.contains("\"finish_reason\":\"tool_calls\""));
+ assertTrue(response.contains("\"name\":\"load_skill\""));
+ assertTrue(response.contains("urlaubsantrag"));
+ }
+
+ private void injectField(Object target, String fieldName, Object value) throws Exception {
+ Class> clazz = target.getClass();
+ while (clazz != null) {
+ try {
+ Field field = clazz.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(target, value);
+ return;
+ } catch (NoSuchFieldException e) {
+ clazz = clazz.getSuperclass();
+ }
+ }
+ throw new NoSuchFieldException(fieldName);
+ }
+}
\ No newline at end of file
diff --git a/workflow/demo/vacation-request-en-1.0.0.bpmn b/workflow/demo/vacation-request-en-1.0.0.bpmn
new file mode 100644
index 0000000..1de18a0
--- /dev/null
+++ b/workflow/demo/vacation-request-en-1.0.0.bpmn
@@ -0,0 +1,1080 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+ StartEvent_1
+
+ DataObject_2
+ event_z9ZfGA
+ task_oHf3cQ
+ task_4ju0eg
+ gateway_0OamEQ
+ event_WuKdrA
+ event_wDw9RA
+ Task_6
+ EndEvent_2
+
+
+
+ Task_3
+ EventBasedGateway_1
+ event_1yYSEQ
+
+
+
+ Task_5
+ IntermediateCatchEvent_6
+ event_81scHA
+ task_HL0KNA
+ IntermediateCatchEvent_17
+ event_QwIjiA
+
+
+
+
+ sequenceFlow_lagafw
+
+
+
+
+
+
+
+
+
+
+
+
+
+]]>
+
+
+
+
+
+
+
+
+
+
+
+ request.subject]]>
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SequenceFlow_23
+ sequenceFlow_4EDjTw
+
+
+
+
+
+
+
+
+
+
+ _name (EAN: _ean) Preis: _price _currency]]>
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Customer: customer.name
+E-Mail: customer.email
+Address: customer.country customer.zip-customer.city customer.address
+
+
+]]>
+
+
+ $workflowgroup - $workflowstatus]]>
+ sequenceFlow_28Et9A
+ sequenceFlow_bc8XPw
+
+
+ SequenceFlow_22
+
+
+
+
+
+
+
+
+
+
+
+ _subject]]>
+
+
+
+
+
+
+
+
+ _amount (Brutto € _amount_brutto)
+_description]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ home
+]]>
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+ sequenceFlow_HCxYvw
+ sequenceFlow_t7vAEA
+
+
+
+
+
+
+
+
+
+
+
+ _subject]]>
+
+
+
+
+
+
+
+
+ _amount (Brutto € _amount_brutto)
+_description]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ home]]>
+
+
+
+
+
+
+
+
+ order.delivery]]>
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sequenceFlow_ptQjOA
+ sequenceFlow_28Et9A
+
+
+
+
+
+
+
+
+
+
+
+ _name (EAN: _ean) Preis: _price _currency]]>
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Customer: customer.name
+E-Mail: customer.email
+Address: customer.country customer.zip-customer.city customer.address
+
+
+]]>
+
+
+ $workflowgroup - $workflowstatus]]>
+ sequenceFlow_gTAnUw
+ SequenceFlow_22
+
+
+
+
+
+ SequenceFlow_23
+
+ sequenceFlow_HCxYvw
+ sequenceFlow_5y8UKw
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+ home]]>
+
+
+
+ sequenceFlow_cAQRKg
+ sequenceFlow_4EDjTw
+
+
+
+
+
+
+
+
+
+
+
+
+
+ request.subject]]>
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Customer: customer.name
+E-Mail: customer.email
+Address: customer.country customer.zip-customer.city customer.address
+
+
+]]>
+
+
+ $workflowgroup - $workflowstatus]]>
+ sequenceFlow_WRPdIA
+ sequenceFlow_bCmSJg
+ sequenceFlow_t7vAEA
+ sequenceFlow_ptQjOA
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ request.subject]]>
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sequenceFlow_WRPdIA
+ sequenceFlow_hkBSvg
+ sequenceFlow_lagafw
+ sequenceFlow_F06l0g
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sequenceFlow_bc8XPw
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ _subject]]>
+
+
+
+
+
+
+
+
+ _amount (Brutto € _amount_brutto)
+_description]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ home
+
+]]>
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+ sequenceFlow_5y8UKw
+ sequenceFlow_gTAnUw
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ request.subject]]>
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Customer: customer.name
+E-Mail: customer.email
+Address: customer.country customer.zip-customer.city customer.address
+
+
+]]>
+
+
+
+ sequenceFlow_WRPdIA
+ sequenceFlow_hkBSvg
+ sequenceFlow_gc3sAw
+ sequenceFlow_lN0z0w
+
+
+
+ sequenceFlow_5y8UKw
+ sequenceFlow_F06l0g
+ sequenceFlow_cAQRKg
+ sequenceFlow_lxIpQw
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+ home]]>
+
+
+
+ sequenceFlow_lxIpQw
+ sequenceFlow_gc3sAw
+
+
+
+ sequenceFlow_lN0z0w
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+ sequenceFlow_InfPdQ
+ sequenceFlow_bCmSJg
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+