       self.removedItemLifetime = removedItemLifetime
     }
   }
-
+}
+
+/// Jelly Bean World (JBW) simulation server.
+public final class SimulationServer {
+  /// Simulation server configuration. This is `nil` if this is not a simulation server.
+  public let configuration: Configuration
+
+  /// Simulator instance used by this server.
+  public let simulator: Simulator
+
+  /// Pointer to the underlying C API simulation server instance.
+  @usableFromInline internal var handle: UnsafeMutableRawPointer?
+
+  /// Creates a new simulation server.
+  ///
+  /// - Parameters:
+  ///   - configuration: Simulation server configuration.
+  ///   - simulator: Simulator instance used by the simulation server.
+  public init(using configuration: Configuration, simulator: Simulator) {
+    self.configuration = configuration
+    self.simulator = simulator
+    self.handle = simulationServerStart(
+      simulator.handle,
+      configuration.port,
+      configuration.connectionQueueCapacity,
+      configuration.workerCount)
+  }
+
+  deinit {
+    if let h = handle { simulationServerStop(h) }
+  }
+
+  /// Stops this simulations server.
+  @inlinable
+  public func stop() {
+    if let h = handle { simulationServerStop(h) }
+  }
+}
+
+extension SimulationServer {
   /// Simulation server configuration.
-  public struct ServerConfiguration {
+  public struct Configuration {
     /// Port in which the simulation server will be listening.
     public let port: UInt32
 

      simulator.agentStates[state.id] = AgentState(fromC: state, for: simulator)
    }
    simulator.dispatchSemaphore?.signal()
  }
}

/// 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.agents = [UInt64: Agent](uniqueKeysWithValues: zip(agentStates.keys, agents))
    simulatorSetStepCallbackData(handle, Unmanaged.passUnretained(self).toOpaque())
  }

  deinit {
    if let h = handle { simulatorDelete(h) }
  }

  /// Adds a new agent to this simulator, and updates
