Softmax runs competitive multi-agent leagues. A policy is a container: the
runner hands it a game socket, the episode ends, the container exits, and it is
never restarted. So a policy replays the same opponents for weeks and starts
every match knowing nothing.
Anona carries what it learned into the next episode.
No extra to install. The half that runs inside the pod uses the standard
library alone.
The shape is different from the other adapters
Every other integration here recalls and stores around a model call. A Softmax
player cannot do that: its pod has no outbound network. DNS does not
resolve, and no proxy is offered, so a policy cannot reach Anona while it plays.
What it can reach is the in-cluster artifact endpoint. Memory therefore leaves
an episode as an artifact, is ingested from outside the cluster, and comes back
into the next generation as an environment variable set at upload time.
The two halves never share a process, and only one of them needs a network.
In your player
mem.generation is 0 on the first run and increments each time recall is
carried in.
Every method degrades to a no-op rather than raising, the same fail-open policy
the framework adapters take. A policy that crashes because memory was
unavailable is strictly worse than one that plays without it, and the middle of
a league episode is not where you want to find out an environment variable was
missing.
Outside the cluster
Then upload the next generation with it:
or let the adapter build the argv:
record_batch is queued, and extraction is one model call per chunk. A
compile() issued straight after ingest_episode() recalls nothing, and the
space looks broken when it is merely still working. Call wait_for_ingest()
between them.
Latency is why this works
Recall is read once before the first action and written once before exit, never
per tick. Retrieve is about 2.1s at p50, far outside a per-decision window, so
the artifact round trip costs a policy nothing it was going to use.
What it gives up is adapting to something first seen in the current episode.
For league play, where the same opponents recur for weeks, that is a small
fraction of the value.
Scoping
One space per coworld, with user_id set to the policy name. That makes the
policy the subject the space accumulates knowledge about, so
get_user_profile(space, policy) is a standing scouting report rather than
something you assemble.
Scoped reads are strict, so use the same convention for every write into a
space or an earlier generation becomes invisible to later ones. See
Scoping.
The artifact URL has two shapes. A hosted episode sets
http://job-<id>-game:9091/...; coworld run-episode sets
file:///coworld-artifact/policy_artifact_0.zip. A client that handles only the
hosted form loses every local write silently, which is where you develop.
flush() handles both.
The artifact endpoint takes PUT, not POST, and answers 201 in about 100ms.
compile() trims the blob to 3000 bytes, dropping the weakest match first,
because the result becomes an environment variable at upload time. An
oversized recall is a deploy failure, not merely a large memory.
What this does not do
It moves information between episodes. Whether recall improves play depends on
a policy able to act on it, which is your side of the problem, not the memory
layer’s.