org.llm4s.agent.testkit

Members list

Type members

Classlikes

trait CheckpointerContract extends AnyFlatSpecLike, Matchers, EitherValues, OptionValues

The contract every org.llm4s.agent.graph.Checkpointer must meet, as a ScalaTest suite. Mix it into a spec, implement newCheckpointer, and every case below runs against your store:

The contract every org.llm4s.agent.graph.Checkpointer must meet, as a ScalaTest suite. Mix it into a spec, implement newCheckpointer, and every case below runs against your store:

class SqliteCheckpointerContractSpec extends AnyFlatSpec with CheckpointerContract:
 protected def newCheckpointer(clock: java.time.Clock): Checkpointer =
   SqliteCheckpointer.open(freshDatabaseFile(), clock)

The cases are the ones the Checkpointer Scaladoc states, in two groups:

  • '''The store''', called directly: commits applied atomically or not at all; a new checkpoint accepted only over the latest (GraphError.CheckpointConflict) and pending writes only for it (GraphError.InvalidCommit); events numbered contiguously in commit order and never reused, also after a refused commit, compaction or deleteThread; compaction and GraphError.ReplayUnavailable; deleteThread; checkpoints, pending writes and events read back as written; and claims and fencing - one live claim per thread, tokens strictly increasing, takeover once a claim has expired by the store's clock, renewal and release by the current token only, and every commit refused with GraphError.StaleClaim unless it carries the current token - including from many threads at once.
  • '''Two runtimes over one store''': a second GraphRuntime is refused a thread whose run is live in the first (GraphError.ThreadBusy naming the holder); a run that keeps renewing is not taken over; once a claim expires, the second runtime recovers the thread without re-running the first run's completed tasks, and the first run's later commits are refused, so it fails with GraphError.CheckpointWriteFailed and leaves nothing in the thread; and of several runtimes recovering one thread at once, exactly one is admitted.

Claim expiry is judged by the store's clock. By default the store under test must use the clock it is given for that (and may use it for nothing else), and the suite moves a ManualClock; it never waits for real time to pass a claim's ttl. A store that judges expiry by a clock it cannot be handed - a database server's now() - overrides advanceStoreClock to make claims expire as if that clock had moved (for example by moving every stored expiry back), and sets exactExpiry to false, so that the cases check expiry by its effect only.

A durable store also overrides reopen, so that the cases about claims and tokens surviving a restart - a process that closes the store and opens the same storage again - run against storage that was really closed. The default reopens nothing and hands the same instance back.

Each case gets a new store from newCheckpointer and uses its own thread ids; the store need not be empty of other threads.

Attributes

Supertypes
trait OptionValues
trait EitherValues
trait Matchers
trait Explicitly
trait MatcherWords
trait Tolerance
trait AnyFlatSpecLike
trait Documenting
trait Alerting
trait Notifying
trait Informing
trait CanVerb
trait MustVerb
trait ShouldVerb
trait TestRegistration
trait TestSuite
trait Suite
trait Serializable
trait Assertions
trait TripleEquals
trait TripleEqualsSupport
class Object
trait Matchable
class Any
Show all
final class ManualClock(start: Instant, zone: ZoneId) extends Clock

A clock that moves only when told to, for driving claim expiry in a test: hand it to the store under test, and advance it past a claim's ttl to let another run take the thread over. Safe to read and move from any thread.

A clock that moves only when told to, for driving claim expiry in a test: hand it to the store under test, and advance it past a claim's ttl to let another run take the thread over. Safe to read and move from any thread.

Attributes

Supertypes
class Clock
trait InstantSource
class Object
trait Matchable
class Any