CtrlK
CommunityDocumentationLog inGet started
Tessl Logo

tessl/maven-com-embabel-agent--embabel-agent-shell

Interactive Spring Shell-based command-line interface for the Embabel Agent platform, providing terminal interaction, chat sessions, and agent management commands.

Overview
Eval results
Files

examples.mddocs/

Complete Examples

Working code examples demonstrating common use cases for Embabel Agent Shell.

Minimal Application

Basic Spring Boot application with shell:

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class MinimalAgentApp

fun main(args: Array<String>) {
    runApplication<MinimalAgentApp>(*args)
}

application.yaml:

embabel:
  agent:
    shell:
      lineLength: 140

Usage:

./gradlew bootRun

embabel> agents
embabel> x "Find news about AI"

Task Execution Service

Service that executes tasks programmatically:

import com.embabel.agent.api.common.autonomy.Autonomy
import com.embabel.agent.api.common.autonomy.AgentProcessExecution
import com.embabel.agent.api.common.autonomy.ProcessOptions
import com.embabel.agent.api.common.autonomy.GoalSelectionOptions
import com.embabel.agent.shell.TerminalServices
import com.embabel.agent.core.AgentPlatform
import org.springframework.stereotype.Service

@Service
class TaskExecutionService(
    private val autonomy: Autonomy,
    private val terminalServices: TerminalServices,
    private val agentPlatform: AgentPlatform
) {
    fun executeTask(intent: String, debug: Boolean = false): AgentProcessExecution {
        val processOptions = ProcessOptions(
            open = false,
            showPrompts = debug,
            showLlmResponses = debug,
            debug = debug,
            toolDelay = false,
            operationDelay = false,
            showPlanning = true,
            blackboard = null
        )

        val goalSeeker = autonomy.createGoalSeeker(
            intent = intent,
            agentScope = agentPlatform,
            goalChoiceApprover = terminalServices,
            goalSelectionOptions = GoalSelectionOptions()
        )

        val outputChannel = terminalServices.outputChannel(agentPlatform)

        return goalSeeker.achieve(
            processOptions = processOptions,
            outputChannel = outputChannel
        )
    }
}

Usage:

@RestController
class TaskController(
    private val taskExecutionService: TaskExecutionService
) {
    @PostMapping("/execute")
    fun execute(@RequestBody request: TaskRequest): TaskResponse {
        val result = taskExecutionService.executeTask(
            intent = request.intent,
            debug = request.debug
        )
        return TaskResponse(
            output = result.output.toString(),
            cost = result.costInfo.totalCost
        )
    }
}

Chat Application

Complete chat application with log management:

import com.embabel.agent.shell.TerminalServices
import com.embabel.chat.Chatbot
import com.embabel.chat.ChatSession
import com.embabel.agent.shell.personality.starwars.StarWarsColorPalette
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.stereotype.Component

@SpringBootApplication
class ChatApplication

fun main(args: Array<String>) {
    runApplication<ChatApplication>(*args)
}

@Component
class ChatRunner(
    private val terminalServices: TerminalServices,
    private val chatbot: Chatbot
) : CommandLineRunner {
    override fun run(vararg args: String?) {
        // Redirect logs during chat
        val restoreLogging = terminalServices.redirectLoggingToFile(
            filename = "chat-session",
            dir = System.getProperty("user.dir")
        )

        try {
            val chatSession = chatbot.createSession()

            val welcome = """
                Welcome to Star Wars Agent Chat!

                May the Force be with you.
                Type 'exit' to end the session.
            """.trimIndent()

            terminalServices.chat(
                chatSession = chatSession,
                welcome = welcome,
                colorPalette = StarWarsColorPalette()
            )
        } finally {
            // Restore original logging
            restoreLogging()
        }
    }
}

application.yaml:

embabel:
  agent:
    shell:
      lineLength: 120
      redirectLogToFile: true
    logging:
      personality: starwars

Custom Prompt Provider

Create and use a custom prompt provider:

import com.embabel.agent.shell.MessageGeneratorPromptProvider
import com.embabel.common.util.MessageGenerator
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.stereotype.Component

// Custom message generator
class RobotMessageGenerator : MessageGenerator {
    private val messages = listOf(
        "System: All systems operational",
        "System: Standing by for commands",
        "System: Ready to assist",
        "System: Processing request",
        "System: Analyzing data"
    )

    override fun generate(): String {
        return messages.random()
    }
}

// Custom prompt provider
@Component
@ConditionalOnProperty(
    name = ["embabel.agent.logging.personality"],
    havingValue = "robot"
)
class RobotPromptProvider : MessageGeneratorPromptProvider(
    prompt = "robot",
    color = 0x00FFFF, // Cyan
    messageGenerator = RobotMessageGenerator()
)

application.yaml:

embabel:
  agent:
    logging:
      personality: robot

Result: Cyan "robot>" prompt with random system messages

Custom Output Formatting

Format process output with custom styling:

import com.embabel.agent.shell.formatProcessOutput
import com.embabel.agent.shell.markdownToConsole
import com.embabel.agent.api.common.autonomy.AgentProcessExecution
import com.embabel.agent.spi.logging.ColorPalette
import com.embabel.agent.spi.logging.DefaultColorPalette
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.stereotype.Service

@Service
class OutputFormattingService(
    private val objectMapper: ObjectMapper,
    private val colorPalette: ColorPalette = DefaultColorPalette()
) {
    fun formatExecutionResult(result: AgentProcessExecution): String {
        return formatProcessOutput(
            result = result,
            colorPalette = colorPalette,
            objectMapper = objectMapper,
            lineLength = 140
        )
    }

    fun formatMarkdown(content: String): String {
        return if (content.contains("#")) {
            markdownToConsole(content)
        } else {
            content
        }
    }

    fun formatWithCustomLineLength(
        result: AgentProcessExecution,
        lineLength: Int
    ): String {
        return formatProcessOutput(
            result = result,
            colorPalette = colorPalette,
            objectMapper = objectMapper,
            lineLength = lineLength
        )
    }
}

Usage:

@Component
class ResultPresenter(
    private val outputFormattingService: OutputFormattingService
) {
    fun presentResult(result: AgentProcessExecution) {
        val formatted = outputFormattingService.formatExecutionResult(result)
        println(formatted)
    }

    fun presentMarkdown(markdown: String) {
        val styled = outputFormattingService.formatMarkdown(markdown)
        println(styled)
    }
}

Interactive Form Handler

Handle interactive forms with validation:

import com.embabel.agent.shell.TerminalServices
import com.embabel.agent.api.common.autonomy.Awaitable
import com.embabel.agent.api.common.autonomy.FormBindingRequest
import com.embabel.agent.api.common.autonomy.ConfirmationRequest
import com.embabel.agent.core.AgentProcess
import org.springframework.stereotype.Service

@Service
class FormHandlerService(
    private val terminalServices: TerminalServices
) {
    fun handleInteractiveRequest(
        awaitable: Awaitable<*, *>,
        agentProcess: AgentProcess
    ): Boolean {
        val response = terminalServices.handleAwaitable(awaitable)

        return if (response != null) {
            awaitable.onResponse(response, agentProcess)
            true
        } else {
            // User cancelled
            false
        }
    }

    fun confirmAction(message: String): Boolean {
        return terminalServices.confirm(message)
    }

    fun confirmWithContext(
        action: String,
        context: Map<String, Any>
    ): Boolean {
        val contextStr = context.entries.joinToString("\n") {
            "  ${it.key}: ${it.value}"
        }
        val message = """
            Confirm action: $action

            Context:
            $contextStr

            Proceed?
        """.trimIndent()

        return terminalServices.confirm(message)
    }
}

Usage:

@Component
class WorkflowExecutor(
    private val formHandlerService: FormHandlerService
) {
    fun executeWithConfirmation() {
        val context = mapOf(
            "task" to "Delete user data",
            "user" to "john@example.com",
            "records" to 150
        )

        if (formHandlerService.confirmWithContext("Delete Data", context)) {
            // Proceed with action
            println("Action confirmed, proceeding...")
        } else {
            println("Action cancelled")
        }
    }
}

Custom Shell Commands

Add custom commands to the shell:

import org.springframework.shell.standard.ShellComponent
import org.springframework.shell.standard.ShellMethod
import org.springframework.shell.standard.ShellOption
import com.embabel.agent.shell.TerminalServices
import com.embabel.agent.api.common.autonomy.Autonomy

@ShellComponent
class CustomCommands(
    private val autonomy: Autonomy,
    private val terminalServices: TerminalServices
) {
    @ShellMethod(
        value = "Quick search for news",
        key = ["news", "n"]
    )
    fun news(
        @ShellOption(help = "Search topic") topic: String
    ): String {
        val intent = "Find recent news about $topic"
        // Execute task and return result
        return "Searching for news about: $topic"
    }

    @ShellMethod(
        value = "Summarize text",
        key = ["summarize", "sum"]
    )
    fun summarize(
        @ShellOption(help = "Text to summarize") text: String,
        @ShellOption(
            value = ["-l", "--length"],
            help = "Summary length",
            defaultValue = "short"
        ) length: String
    ): String {
        val intent = "Summarize the following text in $length form: $text"
        // Execute task and return result
        return "Generating $length summary..."
    }

    @ShellMethod(value = "Interactive task builder")
    fun buildTask(): String {
        terminalServices.print("Task Builder")
        terminalServices.print("============")

        if (terminalServices.confirm("Do you want to search the web?")) {
            terminalServices.print("Web search enabled")
        }

        return "Task configuration complete"
    }
}

Usage:

embabel> news "artificial intelligence"
embabel> n "AI"
embabel> summarize "Long text here..." -l brief
embabel> sum "Text..." --length detailed
embabel> buildTask

Multi-Turn Conversation Handler

Handle multi-turn conversations with state:

import com.embabel.agent.shell.TerminalServices
import com.embabel.chat.Chatbot
import com.embabel.chat.ChatSession
import org.springframework.stereotype.Service

@Service
class ConversationService(
    private val terminalServices: TerminalServices,
    private val chatbot: Chatbot
) {
    fun runConversation(topic: String) {
        val chatSession = chatbot.createSession()

        val welcome = """
            Starting conversation about: $topic

            I'll remember our conversation context.
            Type 'exit' to end.
        """.trimIndent()

        // Redirect logs for clean output
        val restoreLogging = terminalServices.redirectLoggingToFile(
            filename = "conversation-$topic",
            dir = System.getProperty("user.dir")
        )

        try {
            terminalServices.chat(
                chatSession = chatSession,
                welcome = welcome
            )
        } finally {
            restoreLogging()
        }

        println("\nConversation ended. Logs saved to: conversation-$topic.log")
    }

    fun runMultiTopicConversation(topics: List<String>) {
        topics.forEach { topic ->
            println("\n=== Topic: $topic ===\n")
            runConversation(topic)
        }
    }
}

Usage:

@Component
class ConversationRunner(
    private val conversationService: ConversationService
) : CommandLineRunner {
    override fun run(vararg args: String?) {
        val topics = listOf(
            "technology",
            "science",
            "business"
        )

        conversationService.runMultiTopicConversation(topics)
    }
}

Goal Approval Workflow

Implement custom goal approval logic:

import com.embabel.agent.api.common.autonomy.GoalChoiceApprover
import com.embabel.agent.api.common.autonomy.GoalChoiceApprovalRequest
import com.embabel.agent.api.common.autonomy.GoalChoiceApprovalResponse
import com.embabel.agent.api.common.autonomy.GoalChoiceApproved
import com.embabel.agent.api.common.autonomy.GoalChoiceNotApproved
import com.embabel.agent.shell.TerminalServices
import org.springframework.stereotype.Component

@Component
class CustomGoalApprover(
    private val terminalServices: TerminalServices
) : GoalChoiceApprover {

    private val autoApprovedGoals = setOf(
        "search_news",
        "get_weather",
        "calculate"
    )

    override fun approve(
        goalChoiceApprovalRequest: GoalChoiceApprovalRequest
    ): GoalChoiceApprovalResponse {
        val goal = goalChoiceApprovalRequest.goalChoice.goal

        // Auto-approve safe goals
        if (goal.name in autoApprovedGoals) {
            terminalServices.print("Auto-approved goal: ${goal.name}")
            return GoalChoiceApproved(goalChoiceApprovalRequest)
        }

        // Require explicit approval for other goals
        val approved = terminalServices.confirm(
            """
            Approve goal: ${goal.description}

            Goal name: ${goal.name}
            Capabilities: ${goal.capabilities.joinToString()}

            Approve?
            """.trimIndent()
        )

        return if (approved) {
            GoalChoiceApproved(goalChoiceApprovalRequest)
        } else {
            GoalChoiceNotApproved(
                goalChoiceApprovalRequest,
                reason = "User declined approval"
            )
        }
    }
}

Usage:

@Service
class SafeTaskExecutor(
    private val autonomy: Autonomy,
    private val customGoalApprover: CustomGoalApprover,
    private val agentPlatform: AgentPlatform
) {
    fun executeSafely(intent: String): AgentProcessExecution {
        val goalSeeker = autonomy.createGoalSeeker(
            intent = intent,
            agentScope = agentPlatform,
            goalChoiceApprover = customGoalApprover, // Use custom approver
            goalSelectionOptions = GoalSelectionOptions()
        )

        return goalSeeker.achieve(
            processOptions = ProcessOptions(),
            outputChannel = terminalServices.outputChannel(agentPlatform)
        )
    }
}

Custom Output Channel

Create a custom output channel with logging:

import com.embabel.agent.api.channel.OutputChannel
import com.embabel.agent.api.channel.OutputChannelEvent
import com.embabel.agent.api.channel.MessageOutputChannelEvent
import com.embabel.agent.api.channel.ProgressOutputChannelEvent
import com.embabel.agent.api.channel.LoggingOutputChannelEvent
import com.embabel.agent.api.channel.ContentOutputChannelEvent
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component

@Component
class LoggingOutputChannel : OutputChannel {
    private val logger = LoggerFactory.getLogger(javaClass)

    override fun send(event: OutputChannelEvent) {
        when (event) {
            is MessageOutputChannelEvent -> {
                logger.info("[Message] ${event.sender}: ${event.content}")
                println("${event.sender}: ${event.content}")
            }
            is ProgressOutputChannelEvent -> {
                logger.info("[Progress] ${event.message}")
                println("▶ ${event.message}")
            }
            is LoggingOutputChannelEvent -> {
                logger.debug("[Log] ${event.message}")
                println("🪵 ${event.message}")
            }
            is ContentOutputChannelEvent -> {
                logger.info("[Content] ${event.content}")
                println(event.content)
            }
        }
    }
}

@Component
class MultiChannelOutputService(
    private val terminalServices: TerminalServices,
    private val loggingOutputChannel: LoggingOutputChannel,
    private val agentPlatform: AgentPlatform
) {
    fun createMultiChannel(): OutputChannel {
        val terminalChannel = terminalServices.outputChannel(agentPlatform)

        return object : OutputChannel {
            override fun send(event: OutputChannelEvent) {
                // Send to both channels
                terminalChannel.send(event)
                loggingOutputChannel.send(event)
            }
        }
    }
}

Complete Application Example

Full application with all features:

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import com.embabel.chat.Chatbot
import com.embabel.chat.ChatSession

@SpringBootApplication
class CompleteAgentApplication

fun main(args: Array<String>) {
    runApplication<CompleteAgentApplication>(*args)
}

@Configuration
class AppConfiguration {

    @Bean
    fun chatbot(): Chatbot {
        return object : Chatbot {
            override fun createSession(): ChatSession {
                // Return your chat session implementation
                return MyChatSession()
            }
        }
    }
}

application.yaml:

spring:
  application:
    name: complete-agent-app

embabel:
  agent:
    shell:
      lineLength: 140
      redirectLogToFile: true
    logging:
      personality: starwars

logging:
  level:
    com.embabel.agent: INFO
    root: WARN

build.gradle.kts:

plugins {
    kotlin("jvm") version "1.9.20"
    kotlin("plugin.spring") version "1.9.20"
    id("org.springframework.boot") version "3.2.0"
}

dependencies {
    implementation("com.embabel.agent:embabel-agent-shell:0.3.3")
    implementation("org.springframework.boot:spring-boot-starter")
    implementation("org.springframework.shell:spring-shell-starter:3.2.0")
}

Run:

./gradlew bootRun

embabel> agents
embabel> x "Find news" -p
embabel> chat
embabel> exit

Testing Example

Unit test for shell components:

import com.embabel.agent.shell.TerminalServices
import com.embabel.agent.shell.config.ShellProperties
import org.jline.terminal.Terminal
import org.junit.jupiter.api.Test
import org.mockito.Mockito.mock
import kotlin.test.assertEquals

class TerminalServicesTest {

    @Test
    fun testConfirmYes() {
        val terminal = mock(Terminal::class.java)
        val shellProperties = ShellProperties().apply {
            lineLength = 140
        }

        val terminalServices = TerminalServices(terminal, shellProperties)

        // Mock user input "y"
        // (Requires additional mocking setup)

        // Test confirmation logic
        // val result = terminalServices.confirm("Test?")
        // assertEquals(true, result)
    }

    @Test
    fun testLineLength() {
        val shellProperties = ShellProperties().apply {
            lineLength = 100
        }

        assertEquals(100, shellProperties.lineLength)
    }
}

These examples demonstrate common patterns and use cases for the Embabel Agent Shell. Adapt them to your specific needs and requirements.

tessl i tessl/maven-com-embabel-agent--embabel-agent-shell@0.3.0

docs

examples.md

index.md

quickstart.md

troubleshooting.md

tile.json