Best practices for agents

Use these guidelines to optimize your application development with custom agents.

Optionally use a job queue

When you implement a new agent, you have the option to use a job queue table stored in the EDDS database. This approach ensures that the application and agent can share information about job statuses and errors. Implement job queue error handling, which might include displaying an error message. Disable the agent when a critical error occurs.

Note: Store custom applications application queues in the EDDS, rather than at the workspace database level.

Use helper classes

Use the helper classes so that you program to an interface. The helper classes provide functionality for returning a database context and connecting to the Services API. They are available when you reference the Relativity.API.dll in your projects. Code samples for agents illustrate how to establish this connection with the helper classes. See Basic concepts for agents.

Use a Workload discovery endpoint

The Workload Discovery endpoint is the key component for dynamically scaling an agent and allows agents to efficiently leverage resources, allowing them to run more frequently than once per hour. This endpoint communicates with the Relativity platform, providing information about the amount of work required by an agent for a specific tenant at any given time. The Workload Discovery endpoint should support the following behavior:

  • Return within 2 seconds - Use these endpoints only for a quick check of the amount of work an agent has to do. The agent itself should perform any advanced calculations or other processing.
  • Accurately reflect the workload size - The returned size should always accurately reflect the amount of work the agent has to complete at a given time. Any large variations result in suboptimal scaling behavior. See Agent Scaling Guidelines.

    Note: When there is no work to be done, it is very important for the endpoint to return a Size of None.

Avoid creating App Domains

Avoid the use of App Domains since this may result in undesired behavior. You may create threads and thread pools.

Return from the Execute method

Your agent must return from the Execute method once all work or tasks are completed. Using a loop to process a queue of work is acceptable if the loop and the Execute method are exited once no more work remains.

Agent processes may be destroyed once the Execute method of an Agent returns. Therefore, any threads outliving Execute will likely be interrupted before they can finish. Spinning off additional threads will work correctly, but they must rejoin the main thread before Execute returns. If you use threads, you must carefully manage their lifecycle.

Do not expose HTTP endpoints

If you need an API for your agent, you should create a Kepler service and use the database or another shared data store to communicate information between your agent and your Kepler service.

Agents should be ephemeral and fault-tolerant

Agents may be preempted and restarted at any time. In practice, this happens infrequently, but you will need to ensure that your agent is resilient to these operations and that these operations will not result in any data integrity issues. Your agent must be able to recover from preemption in part of its workflow and process the remaining work correctly. Agents must also be architected such that they do not rely on long-lived information stored in-memory or in scratch disk. Agents should use cloud storage solutions for anything but small, short-lived scratch space, as heavy usage of scratch space may cause performance issues. See the topic Agent Resiliency Guidelines for guidance.

Do not rely on Agent IDs

Agent IDs are not fixed as part of the context and you cannot rely on any persistent metadata for a particular running instance of an agent. If you require persistence between multiple stages of your business logic, you should create queues to manage the tasks.

Establish an appropriate execution pattern

Agents leveraging the run interval to pick up work should be performing background jobs that do not require a rapid response. The minimum run interval is 1 minute if you have a Workload Discovery endpoint. Otherwise, the minimum interval is 60 minutes. See Agent Scaling Guidelines.

If your agent can't wait for up to a one minute delay before starting to process work, your code should leverage the Agent Status Manager API. Calling this endpoint will immediately kick off your agent so that no delay is required. See Execution patterns for agents

Use fully qualified domain names when accessing fileshares

Referencing files via non-FQDN paths (e.g. \\files\t002\...) will no longer function in RelativityOne Compute. All interactions with files on the fileshare must use FQDN paths.

Do not assume fileshare is available before first operation

Agents will not connect by default to any file shares listed as Resource Servers. A fileshare connection will only be established when an agent makes an IO operation against the fileshare (e.g. checking if a file exists).

Agents which assume a file share connection exists in code executing outside of the Agent process before the Agent itself makes an IO operation on that file share will receive a "file share not found" error.

Copy
View the code sample
using System;
using System.Collections.Generic;
using kCura.Agent;
using Relativity.API;
using System.Management;
using System.IO;
namespace ExampleAgent
{
    [kCura.Agent.CustomAttributes.Name("ExternalProcessFileAccessAgent")]
    //[System.Runtime.InteropServices.Guid("00000000-YOUR-0000-GUID-000000000000")]
    public class ExternalProcessFileAccessAgent : AgentBase
    {
        private static List<string> _alreadyMountedFilePaths;
        public override string Name => "ExternalProcessFileAccessAgent";

        public ExternalProcessFileAccessAgent()
        {
            _alreadyMountedFilePaths = new List<string>();
        }

        public override void Execute()
        {
            // ...

            string filePath = GetPathToRelativityFile();

            // The new code that does a simple System.IO operation against the fileshare path before allowing the external process to make the attempt.
            EnsureFileShareIsMounted(filePath);
            // Now you can fire off whatever external process you were doing before because the fileshare mounting would have already happened.
            ExecuteExternalProcessThatConnectsToFileShare(filePath);

            // ...
        }


        private void EnsureFileShareIsMounted(string filePath)
        {
            // For simplicity, this code assumes the paths will always be the correct Relativity fileshare format.
            // output for this example would be "files.T001.ctus0123456.relativity.one"
            string pathHost = new Uri(filePath).Host;

            if (!_alreadyMountedFilePaths.Contains(pathHost))
            {
                try
                {
                    // Adding any basic System.IO file/directory connection for the file path before kicking off the external process.
                    // This example uses a basic Directory.Exists to access the fileshare before handing off the path to an external process.
                    // Under the hood, System.IO operations are monitored for, and the first unique base path hit (Example: \\files.T001.ctus0123456.relativity.one)
                    //   will attempt to mount the fileshare to the Agent container
                    System.IO.Directory.Exists(filePath);

                    // Keeping track of the UNC root path of the fileShares that have already been mounted can avoid making unnecessary extra calls to the fileshare.
                    // If you are only hitting a few paths per each agent execution, it is probably ok to skip keeping track of the previously mounted paths.
                    _alreadyMountedFilePaths.Add(pathHost);
                }
                catch
                {
                    // Log that the Directory lookup failed. 
                }
            }            
        }

        // Code past this line is used only for the example.

        // Using Powershell as an example process that runs as its own process and not this current agent process.
        private void ExecuteExternalProcessThatConnectsToFileShare(string filePath)
        {
            System.Management.Automation.PowerShell.Create().AddCommand("Get-Content").AddParameter("-Path", filePath).Invoke();
        }

        // You would already be doing something to get the Relativity file paths. This is just a static example.
        private string GetPathToRelativityFile()
        {
            string exampleRelativityFilePath = @"\\files.T001.ctus0123456.relativity.one\T001\Files\EDDS654321\XX_253B6AE8-2729-44E1-8D6D-0B23C2753B88\CB3A1DD8-390A-4A20-BCC4-1EF67EC6A9F3";
            return exampleRelativityFilePath;
        }
    }
}