       self.port = port
       self.connectionQueueCapacity = connectionQueueCapacity
       self.workerCount = workerCount
+    }
+  }
+}
+
+/// Simulation agent. Agents can interact with the Jelly Bean World by being added to simulators
+/// that manage them.
+public protocol Agent {
+  /// Returns the agent's desired next action. This function is called automatically by all
+  /// simulators managing this agent, whenever the `Simulator.step()` function is invoked.
+  ///
+  /// - Parameter state: Current state of the agent.
+  /// - Returns: Action requested by the agent.
+  ///
+  /// - Note: The action is not actually performed until the simulator advances by a time step and
+  ///   issues a notification about that event. The simulator only advances by a time step only
+  ///   once all agents have requested some action.
+  mutating func act(using state: AgentState) -> Action
+}
+
+/// State of an agent in the Jelly Bean World.
+public struct AgentState {
+  /// Position of the agent.
+  public let position: Position
+
+  /// Direction in which the agent is facing.
+  public let direction: Direction
+
+  /// Scent that the agent smells. This is a vector with size `S`, where `S` is the scent vector
+  /// size (i.e., the scent dimensionality).
+  public let scent: ShapedArray<Float>
+
+  /// Visual field of the agent. This is a matrix with shape `[V + 1, V + 1, C]`, where `V` is the
+  /// visual range of the agent and `C` is the color vector size (i.e., the color dimensionality).
+  public let vision: ShapedArray<Float>
+
+  /// Items that collected by the agent so far represented as a dictionary mapping items to counts.
+  public let items: [Item: Int]
+}
+
+/// Action that can be taken by agents in the jelly bean world.
+public enum Action {
+  /// No action.
+  case none
+
+  /// Move action, along the specified direction and for the provided number of steps.
+  case move(direction: Direction, stepCount: Int = 1)
+
+  /// Turn action (without any movement to a different cell).
+  case turn(direction: TurnDirection)
+
+  /// Invokes this action in the simulator.
+  ///
+  /// - Parameters:
+  ///   - simulatorHandle: Pointer to the C simulator instance.
+  ///   - clientHandle: Pointer to the C simulator client instance (if a client is being used).
+  ///   - agentID: Identifier of the agent for which to invoke this action.
+  @inlinable
+  internal func invoke(
+    simulatorHandle: UnsafeMutableRawPointer?,
+    clientHandle: UnsafeMutableRawPointer?,
+    agentID: UInt64
+  ) {
+    switch self {
+    case .none:
+      simulatorNoOpAgent(simulatorHandle, clientHandle, agentID)
+    case let .move(direction, stepCount):
+      simulatorMoveAgent(
+        simulatorHandle,
+        clientHandle,
+        agentID,
+        direction.toC(),
+        UInt32(stepCount))
+    case let .turn(direction):
+      simulatorTurnAgent(simulatorHandle, clientHandle, agentID, direction.toC())
     }
   }
 }

      self.removedItemLifetime = removedItemLifetime
    }
  }

  /// Simulation server configuration.
  public struct ServerConfiguration {
    /// Port in which the simulation server will be listening.
    public let port: UInt32


  case front = 0, back, left, right
}

/// Conflict resolution policy for when two or more agents request to move to the same location.
public enum MoveConflictPolicy: UInt32 {
  case noCollisions = 0, firstComeFirstServe, random
}

/// Policy used to indicate whether an action is allowed, disallowed, or ignored. If an action is
/// disallowed, then attempting to perform it will immediately fail, preventing the simulator from
/// progressing if an agent has not performed an action during the current time step. If an action
/// is ignored, then the agent will perform a no-op for that time step (i.e., the simulator will
/// simply ignore that action).
public enum ActionPolicy: UInt32 {
  case allowed = 0, disallowed, ignored
}
