Font Size:
Ask Joget AI

System Agents & Traceability

Overview

The System Agent framework enables request-scoped AI agents to be executed independently of application-bound builder definitions. These agents are designed for system-level use cases such as live UI assistance, agentic editing, automation, and real-time orchestration.

Key characteristics:

  • Each agent execution is identified by a runId
  • Multiple agents can execute concurrently without interference
  • Full execution traceability is available in real time
  • Agents are not tied to app definitions and are instead persisted in a standalone table

This design allows System Agents to behave as first-class system services rather than application plugins.

System Agent Lifecycle

A System Agent execution follows this lifecycle:

  1. runId creation (caller-owned)
  2. Trace subscription (optional, UI-agnostic)
  3. Agent definition assembly (in-memory)
  4. Execution via AgentBuilder
  5. Trace events published during execution

The system agent owns the lifecycle of a single run and guarantees isolation between runs.

runId as the Primary Execution Boundary

The runId is the fundamental unit of isolation:

  • Generated by the caller (frontend or backend service)
  • Passed explicitly through:
    • SystemAgentRequest
    • AgentBuilder config
    • LLMConfig

This guarantees:

  • Safe concurrent execution
  • No cross-run state corruption
  • Deterministic trace attribution

Each runId maps to exactly one agent execution.

Traceability Architecture

Traceable Aspect

The @Traceable aspect instruments agent components:

  • LLMs
  • Prompts
  • Tools
  • Enhancers

For each invocation, it emits structured trace events:

  • RUNNING
  • SUCCESS
  • FAILED
  • END

Trace events include:

  • runId
  • nodeId
  • parentId
  • component type
  • request payload (when available)
  • response or error

Not all traceable methods require request parameters; the aspect extracts payloads defensively based on component type.

Event Publishing

Trace events are published via an Event Bus topic keyed by runId.

This enables:

  • Multiple subscribers per run
  • Real-time streaming
  • UI-agnostic consumption (WebSocket, SSE, logs, etc.)

SystemAgent API

Execution

A System Agent execution is started via:

  • SystemAgent.run(SystemAgentRequest, properties)

Internally:

  • The agent definition is built in-memory
  • The definition is executed via AgentBuilder
  • The runId is passed explicitly
  • Execution context is cleaned up in finally

Subscription Model

Consumers may subscribe to a run before execution:

  • subscribe(runId, Consumer<String>)
  • Messages are delivered from the Event Bus

Subscriptions are optional and fully decoupled from execution.

How to Create System Agent Request

  • Concrete the agent class, Extend SystemAgentAbstract to define your agent identity

    public class MyTaskAgent extends SystemAgentAbstract { 
    @Override protected String getAgentName() { 
    return "My Task Agent"; } 
    }
  • Build the LLM, you need to pass the LLM provider name(for custom implmentation) or create it with static class in AgentComponent(it contain all official agent builder elements support)

    // Load active LLM config from central config(create llm based on current active llm in AI Service)
    PluginManager pluginManager = (PluginManager) AppUtil.getApplicationContext().getBean("pluginManager");
    
    LLMConfig llmConfig = LLMConfigFactory.createByServiceOrActive(null); 
    
    String providerName = llmConfig.getProviderName();, "Anthropic"
    String apiUrl = llmConfig.getCustomEndpointUrl().isEmpty()
            ? llmConfig.getApiEndpoint()
            : llmConfig.getCustomEndpointUrl();
    
    Map<String, Object> llmProperties = new HashMap<>();
    llmProperties.put("apiKey",  llmConfig.getApiKey());
    llmProperties.put("model",   llmConfig.getModelName());
    llmProperties.put("apiUrl",  apiUrl);
    
    AgentLLMAbstract llm = AgentFactory.createLLM(pluginManager, providerName, llmProperties);
    
    // create with Agent Component static method 
    AgentLLMAbstract llm = AgentFactory.create(pluginManager, AgentComponent.OpenAIAgentLLM, llmProperties);
    AgentComponent.OpenAIAgentLLM, llmProperties);
  • Build Prompts

    // System prompt — defines the agent's role/persona
    AgentPromptAbstract systemPrompt = AgentFactory.create(
        pluginManager,
        AgentComponent.PersonaAgentPrompt,
        Map.of(
            "role",      "My Assistant",
            "backstory", "You are an expert in ..."
        )
    );
    
    // Task prompt — the actual user request
    AgentPromptAbstract taskPrompt = AgentFactory.create(
        pluginManager,
        AgentComponent.TextPromptAgentPrompt,
        Map.of(
            "msgRole", "USER",
            "prompt",  userInputText
        )
    );
    
    List<AgentPromptAbstract> agentPrompts = List.of(systemPrompt);
    List<AgentPromptAbstract> taskPrompts  = List.of(taskPrompt);
    
  • Build tool and enhancer

    // Tools — functions the agent can call (pass empty list if none)
    List<AgentToolAbstract> tools = List.of();
    
    // Enhancers — post-process the response (e.g. extract JSON)
    AgentEnhancerAbstract enhancer = AgentFactory.create(
        pluginManager,
        AgentComponent.JsonExtractorAgentEnhancer,
        Map.of(
            "llmParse", "Yes",
            "prompt",   "Return ONLY a valid JSON object with keys: result (string)..."
        )
    );
    List<AgentEnhancerAbstract> enhancers = List.of(enhancer);
  • Build the Request and Run

    MyTaskAgent agent = new MyTaskAgent();
    String runId = UUID.randomUUID();
    
    // (Optional) Subscribe to trace events for streaming/logging
    UUID listenerId = agent.subscribe(runId, payload -> {
        // payload is a JSON string with execution trace details
        System.out.println("Trace: " + payload);
    });
    
    // Assemble the request
    SystemAgentRequest req = new SystemAgentRequest(
        llm,
        agentPrompts,
        taskPrompts,
        tools,
        enhancers,
        true    // debugMode
    );
    req.setRunId(runId);
    
    // Execute
    try {
        Response response = agent.run(req, new HashMap<>());
        String content = response.getContent();
        // ... process content
    } finally {
        // Always unsubscribe to prevent memory leaks
        agent.unsubscribe(runId, listenerId);
    }
    

Persistence Model

System Agents are persisted as Builder Definitions, but in a separate table.

Instead:

  • They are stored in a standalone system table
  • Not linked to apps, versions, or workflows

Rationale:

  • System agents are system-level services
  • They must be executable outside the app context
  • They are versioned and managed independently

This separation prevents app lifecycle changes from impacting system agent behavior.

Concurrency & Safety

The architecture guarantees:

  • Multiple agents can run simultaneously
  • The same thread can execute multiple runs
  • No global mutable state
  • No reliance on execution stacks
  • Deterministic trace attribution

The system is safe for:

  • Executor pools
  • Async execution
  • Nested agent calls

Intended Use Cases

  • Agentic UI editing
  • Real-time form or JSON manipulation
  • System assistants
  • Live orchestration agents
  • Background automation

Summary

System Agents provide a clean, isolated, and traceable execution model for system-level AI agents.

Key design principles:

  • runId-driven isolation
  • Explicit context ownership
  • Real-time trace streaming
  • No app coupling

This makes the framework suitable for concurrent, observable AI workflows.

Created by Debanraj Ravindran Last modified by Debanraj Ravindran on Apr 24, 2026