Index: protocol/src/main/java/geniusweb/protocol/NegoSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/NegoSettings.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/NegoSettings.java	(revision 31)
@@ -5,10 +5,8 @@
 import com.fasterxml.jackson.annotation.JsonSubTypes;
 import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
 
 import geniusweb.protocol.session.SessionSettings;
 import geniusweb.protocol.tournament.TournamentSettings;
-
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-
 import tudelft.utilities.logging.Reporter;
 
@@ -36,5 +34,5 @@
 	/**
 	 * @param logger the logger where the protocol can log events to.
-	 * @return the an initialized and ready to use {@link NegoProtocol} that can
+	 * @return an initialized and ready to use {@link NegoProtocol} that can
 	 *         handle this Negotiation.
 	 */
Index: protocol/src/main/java/geniusweb/protocol/session/DefaultSessionState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/DefaultSessionState.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/DefaultSessionState.java	(revision 31)
@@ -1,5 +1,8 @@
 package geniusweb.protocol.session;
 
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -10,32 +13,29 @@
 import geniusweb.progress.Progress;
 import geniusweb.protocol.ProtocolException;
-import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
 import geniusweb.references.PartyWithProfile;
 
 /**
+ * 
+ */
+/**
+ * 
  * The default current state of the session. immutable.
+ * 
+ * @param <P> the actual SessionState object
+ * @param <S> the actual SessionSettings object
  */
-public abstract class DefaultSessionState implements SessionState {
+public abstract class DefaultSessionState<P extends DefaultSessionState<P, S>, S extends SessionSettings>
+		implements SessionState {
 	private final List<Action> actions;
 
-	private final transient ProtocolToPartyConnections connections;
+	private final List<PartyId> connections;
 
 	private final Progress progress;
 
-	private final SessionSettings settings;
+	private final S settings;
 
 	private final ProtocolException error;
 
 	private final Map<PartyId, PartyWithProfile> partyprofiles;
-
-	// only for deserializer.
-	protected DefaultSessionState() {
-		actions = null;
-		connections = null;
-		progress = null;
-		settings = null;
-		partyprofiles = null;
-		error = null;
-	}
 
 	/**
@@ -59,14 +59,14 @@
 	 *                      result in a throw and terminate the session.
 	 */
-	public DefaultSessionState(List<Action> actions,
-			ProtocolToPartyConnections conns, Progress progr,
-			SessionSettings settings,
+	public DefaultSessionState(List<Action> actions, List<PartyId> conns,
+			Progress progr, S settings,
 			Map<PartyId, PartyWithProfile> partyprofiles, ProtocolException e) {
 		if (partyprofiles == null) {
-			partyprofiles = Collections.emptyMap();
+			this.partyprofiles = Collections.emptyMap();
+		} else {
+			this.partyprofiles = partyprofiles;
 		}
 		if (conns == null) {
-			this.connections = new ProtocolToPartyConnections(
-					Collections.emptyList());
+			this.connections = Collections.emptyList();
 		} else {
 			this.connections = conns;
@@ -78,19 +78,41 @@
 		}
 
-		if (!connections.allunique()) {
+		if (connections.size() != new HashSet<PartyId>(connections).size()) {
 			throw new IllegalArgumentException(
 					"There can not be multiple connections for a party:"
 							+ connections);
 		}
+		if (settings == null)
+			throw new IllegalArgumentException("Settings must be not null");
 		this.progress = progr;
 		this.settings = settings;
-		this.partyprofiles = partyprofiles;
 		this.error = e;
 	}
 
 	/**
+	 * Construct a new session state, where the DefaultSessionState changes
+	 * while the rest of the state remains unchanged. Notice the return type P.
+	 * 
+	 * @param actions1       the new {@link Action}s
+	 * @param conns          the new connected {@link PartyId}s
+	 * @param progress1      the new {@link Progress}
+	 * @param settings1      the new {@link SessionSettings}. Normally this is
+	 *                       constant during a session.
+	 * @param partyprofiles1 the new {@link PartyWithProfile}s for all parties.
+	 *                       Normally this remains constant during a session.
+	 * @param e              an error that occured that caused the session to
+	 *                       reach its terminated/final state. If an error does
+	 *                       not cause termination, only a warning should be
+	 *                       logged.
+	 * @return the new state of the derived sessionstate.
+	 */
+	abstract public P with(List<Action> actions1, List<PartyId> conns,
+			Progress progress1, S settings1,
+			Map<PartyId, PartyWithProfile> partyprofiles1, ProtocolException e);
+
+	/**
 	 * @return existing connections.
 	 */
-	public ProtocolToPartyConnections getConnections() {
+	public List<PartyId> getConnections() {
 		return connections;
 	}
@@ -120,5 +142,5 @@
 
 	@Override
-	public SessionSettings getSettings() {
+	public S getSettings() {
 		return settings;
 	}
@@ -137,6 +159,86 @@
 	public String toString() {
 		return this.getClass().getSimpleName() + "[" + actions + ","
-				+ connections + "," + progress + "," + settings + "," + error
-				+ "]";
+				+ connections + "," + progress + "," + settings + "," + error;
+	}
+
+	public P withDeadlineReached() {
+		return with(actions, connections, progress, settings, partyprofiles,
+				error);
+	}
+
+	public P withoutParty(PartyId party) {
+		List<PartyId> newconn = new ArrayList<>(connections);
+		newconn.remove(party);
+		return with(actions, newconn, progress, settings, partyprofiles, error);
+	}
+
+	/**
+	 * 
+	 * @param e the error that occured
+	 * @return a new state with the error set/updated.
+	 */
+	public P with(ProtocolException e) {
+		return with(actions, connections, progress, settings, partyprofiles, e);
+	}
+
+	public P with(PartyId connection, PartyWithProfile partyprofile) {
+		List<PartyId> newconns = new ArrayList<>(getConnections());
+		newconns.add(connection);
+		Map<PartyId, PartyWithProfile> newprofiles = new HashMap<>(
+				getPartyProfiles());
+		newprofiles.put(connection, partyprofile);
+		return with(getActions(), newconns, getProgress(), getSettings(),
+				newprofiles, null);
+	}
+
+	/**
+	 * Sets the new progress for this session.
+	 * 
+	 * @param newprogress the new progress
+	 * @return new SAOPState with the progress set to new value
+	 */
+	public P with(Progress newprogress) {
+		if (newprogress == null) {
+			throw new IllegalArgumentException("newprogress must be not null");
+		}
+		return with(actions, connections, newprogress, settings, partyprofiles,
+				error);
+	}
+
+	/**
+	 * @param actor  the actor that did this action. Can be used to check if
+	 *               action is valid. NOTICE caller has to make sure the current
+	 *               state is not final.
+	 * @param action the action that was proposed by actor.
+	 * @return new SAOPState with the action added as last action.
+	 */
+
+	public P with(PartyId actor, Action action) {
+		String msg = checkAction(actor, action);
+		if (msg != null)
+			throw new IllegalArgumentException(msg);
+
+		List<Action> newactions = new LinkedList<>(getActions());
+		newactions.add(action);
+		return with(newactions, connections, progress, settings, partyprofiles,
+				error);
+	}
+
+	/**
+	 * 
+	 * @param actor  the known real actor that did this action
+	 * @param action an {@link Action}
+	 * @return null if action seems ok, or message explaining why not.
+	 */
+	public String checkAction(PartyId actor, Action action) {
+		if (actor == null) { // this is a bug
+			return "actor must not be null";
+		}
+		if (action == null) {
+			return "action is null";
+		}
+		if (!actor.equals(action.getActor()))
+			return "act contains wrong credentials: " + action;
+		return null;
 	}
 
Index: protocol/src/main/java/geniusweb/protocol/session/SessionSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/SessionSettings.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/SessionSettings.java	(revision 31)
@@ -7,9 +7,9 @@
 import geniusweb.protocol.NegoSettings;
 import geniusweb.protocol.session.amop.AMOPSettings;
+import geniusweb.protocol.session.learn.LearnSettings;
 import geniusweb.protocol.session.mopac.MOPACSettings;
 import geniusweb.protocol.session.mopac2.MOPAC2Settings;
 import geniusweb.protocol.session.saop.SAOPSettings;
 import geniusweb.protocol.session.shaop.SHAOPSettings;
-import geniusweb.protocol.tournament.allpermutations.Team;
 import geniusweb.references.PartyWithProfile;
 import tudelft.utilities.logging.Reporter;
@@ -22,9 +22,10 @@
 		@JsonSubTypes.Type(value = AMOPSettings.class),
 		@JsonSubTypes.Type(value = MOPACSettings.class),
-		@JsonSubTypes.Type(value = MOPAC2Settings.class) })
+		@JsonSubTypes.Type(value = MOPAC2Settings.class),
+		@JsonSubTypes.Type(value = LearnSettings.class) })
 public interface SessionSettings extends NegoSettings {
 
 	/**
-	 * Get the {@link Team}s.
+	 * Get the {@link TeamInfo}s.
 	 * 
 	 * @return list of {@link PartyWithProfile} items.
@@ -33,7 +34,7 @@
 
 	/**
-	 * @return the size of each {@link Team}. Usually 1 (SAOP etc) or sometimes
-	 *         2 (SHAOP). Used eg by tournament runner to determine proper team
-	 *         construction.
+	 * @return the size of each {@link TeamInfo}. Usually 1 (SAOP etc) or
+	 *         sometimes 2 (SHAOP). Used eg by tournament runner to determine
+	 *         proper team construction.
 	 */
 	Integer getTeamSize();
Index: protocol/src/main/java/geniusweb/protocol/session/SessionState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/SessionState.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/SessionState.java	(revision 31)
@@ -10,4 +10,5 @@
 import geniusweb.protocol.NegoState;
 import geniusweb.protocol.session.amop.AMOPState;
+import geniusweb.protocol.session.learn.LearnState;
 import geniusweb.protocol.session.mopac.MOPACState;
 import geniusweb.protocol.session.saop.SAOPState;
@@ -33,5 +34,6 @@
 		@JsonSubTypes.Type(value = SHAOPState.class),
 		@JsonSubTypes.Type(value = AMOPState.class),
-		@JsonSubTypes.Type(value = MOPACState.class) })
+		@JsonSubTypes.Type(value = MOPACState.class),
+		@JsonSubTypes.Type(value = LearnState.class) })
 public interface SessionState extends NegoState {
 
Index: protocol/src/main/java/geniusweb/protocol/session/TeamInfo.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/TeamInfo.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/TeamInfo.java	(revision 31)
@@ -1,7 +1,9 @@
 package geniusweb.protocol.session;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
@@ -11,4 +13,6 @@
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
 
+import geniusweb.protocol.tournament.Team;
+import geniusweb.references.Parameters;
 import geniusweb.references.PartyWithParameters;
 import geniusweb.references.PartyWithProfile;
@@ -23,5 +27,5 @@
 public class TeamInfo {
 
-	private List<PartyWithProfile> parties;
+	private final List<PartyWithProfile> parties;
 
 	/**
@@ -86,3 +90,27 @@
 		return true;
 	}
+
+	/**
+	 * @param parameters a set of {@link Parameters} to be merged with the
+	 *                   existing parameters of the first team member.
+	 * @return updated TeamInfo
+	 */
+	public TeamInfo with(Parameters parameters) {
+		List<PartyWithProfile> updatedparties = new ArrayList<>(parties);
+		PartyWithProfile party1 = updatedparties.get(0);
+		Parameters newparams1 = party1.getParty().getParameters()
+				.with(parameters);
+		PartyWithProfile party1update = new PartyWithProfile(
+				party1.getParty().with(newparams1), party1.getProfile());
+		updatedparties.set(0, party1update);
+		return new TeamInfo(updatedparties);
+	}
+
+	/**
+	 * @return the Team, without the profiles
+	 */
+	public Team getTeam() {
+		return new Team(parties.stream().map(pwithp -> pwithp.getParty())
+				.collect(Collectors.toList()));
+	}
 }
Index: protocol/src/main/java/geniusweb/protocol/session/amop/AMOP.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/amop/AMOP.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/amop/AMOP.java	(revision 31)
@@ -48,8 +48,8 @@
  * specified initially in the settings do participate.
  * 
- * <h1>parameter</h1> AMOP parties can receive a parameter: minVotes containing
+ * <h2>parameter</h2> AMOP parties can receive a parameter: minVotes containing
  * a Double. If set, the AMOP protocol checks that all {@link Votes} from that
  * party have {@link Vote#getMinPower()} &gt; minVotes.
- * <h1>Protocol steps</h1>
+ * <h2>Protocol steps</h2>
  * <ol>
  * <li>The protocol tries to start all parties. If not all parties start, the
Index: protocol/src/main/java/geniusweb/protocol/session/learn/Learn.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/learn/Learn.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/session/learn/Learn.java	(revision 31)
@@ -0,0 +1,310 @@
+package geniusweb.protocol.session.learn;
+
+import java.io.IOException;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.stream.Collectors;
+
+import geniusweb.actions.Action;
+import geniusweb.actions.PartyId;
+import geniusweb.deadline.Deadline;
+import geniusweb.events.ProtocolEvent;
+import geniusweb.inform.Finished;
+import geniusweb.inform.Inform;
+import geniusweb.inform.Settings;
+import geniusweb.progress.ProgressFactory;
+import geniusweb.protocol.CurrentNegoState;
+import geniusweb.protocol.NegoState;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConnFactory;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
+import geniusweb.protocol.session.SessionProtocol;
+import geniusweb.references.Parameters;
+import geniusweb.references.PartyWithProfile;
+import geniusweb.references.ProfileRef;
+import geniusweb.references.ProtocolRef;
+import geniusweb.references.Reference;
+import tudelft.utilities.listener.DefaultListenable;
+import tudelft.utilities.logging.Reporter;
+import tudelft.utilities.repository.NoResourcesNowException;
+
+/**
+ * The Learn protocol allows parties to learn until the deadline set in the
+ * {@link LearnSettings}.
+ */
+public class Learn extends DefaultListenable<ProtocolEvent>
+		implements SessionProtocol {
+	public static final int MIN_SLEEP_TIME = 1000;
+	public static final int MAX_SLEEP_TIME = 60000;
+	public static final int TIME_MARGIN = 20;// ms extra delay after deadline
+	private LearnState state;
+	private final Reporter log;
+	private static final ProtocolRef LEARN = new ProtocolRef("Learn");
+	private volatile Timer deadlinetimer = null;
+	private volatile AtomicBoolean isFinishedInfoSent = new AtomicBoolean(
+			false);
+	private volatile ProtocolToPartyConnections conns = new ProtocolToPartyConnections(
+			Collections.emptyList());
+
+	public Learn(LearnState state, Reporter logger) {
+		this.state = state;
+		this.log = logger;
+	}
+
+	@Override
+	public void start(ProtocolToPartyConnFactory connectionfactory) {
+		try {
+			connect(connectionfactory);
+			setDeadline();
+			setupParties();
+		} catch (Throwable e) {
+			handleError("Failed to start up session", null, e);
+		}
+
+	}
+
+	@Override
+	public String getDescription() {
+		return "Sends all parties a ";
+	}
+
+	@Override
+	public NegoState getState() {
+		return state;
+	}
+
+	@Override
+	public ProtocolRef getRef() {
+		return LEARN;
+	}
+
+	@Override
+	public void addParticipant(PartyWithProfile party) {
+		throw new IllegalStateException(
+				"Dynamic joining a negotiation is not allowed in LEARN");
+	}
+
+	/*******************************************************************
+	 * private functions. Some are protected only, for testing purposes
+	 ********************************************************************/
+	/**
+	 * step 1 in protocol: connect all involved parties and start the clock.
+	 * This always "succeeds" with a valid (but possibly final) state
+	 * <p>
+	 * This is 'protected' to allow junit testing, this code is not a 'public'
+	 * part of the interface.
+	 * 
+	 * @param connectionfactory the connectionfactory for making party
+	 *                          connections
+	 * 
+	 * @throws InterruptedException if the connection procedure is unterrupted
+	 * 
+	 * @throws IOException          if this fails to properly conect to the
+	 *                              parties, eg interrupted or server not
+	 *                              responding..
+	 */
+	protected synchronized void connect(
+			ProtocolToPartyConnFactory connectionfactory)
+			throws InterruptedException, IOException {
+		List<PartyWithProfile> participants = state.getSettings()
+				.getAllParties();
+		List<Reference> parties = participants.stream()
+				.map(parti -> (parti.getParty().getPartyRef()))
+				.collect(Collectors.toList());
+		List<ProtocolToPartyConn> connections = null;
+		log.log(Level.INFO, "SAOP connect " + parties);
+		while (connections == null) {
+			try {
+				connections = connectionfactory.connect(parties);
+			} catch (NoResourcesNowException e) {
+				long waitms = e.getLater().getTime()
+						- System.currentTimeMillis();
+				log.log(Level.INFO,
+						"No resources available to run session, waiting"
+								+ waitms);
+				Thread.sleep(Math.min(MAX_SLEEP_TIME,
+						Math.max(MIN_SLEEP_TIME, waitms)));
+			}
+		}
+		for (int i = 0; i < participants.size(); i++) {
+			conns = conns.with(connections.get(i));
+			setState(this.state.with(connections.get(i).getParty(),
+					participants.get(i)));
+		}
+	}
+
+	/**
+	 * This is called when one of the party connections does an action.
+	 * Synchronized so that we always handle only 1 action at a time.
+	 * 
+	 * @param partyconn the connection on which the action came in.
+	 * @param action    the {@link Action} taken by some party
+	 */
+	protected synchronized void actionRequest(
+			final ProtocolToPartyConn partyconn, final Action action) {
+		if (action == null) {
+			Throwable err = partyconn.getError();
+			if (err == null) {
+				err = new ProtocolException("Party sent a null action",
+						partyconn.getParty());
+			}
+			handleError(partyconn + "Protocol error", partyconn.getParty(),
+					err);
+			return;
+		}
+
+		try {
+			setState(state.with(partyconn.getParty(), action));
+		} catch (Throwable e) {
+			handleError("failed to handle action " + action,
+					partyconn.getParty(), e);
+		}
+
+	}
+
+	/**
+	 * step 2 in protocol: listen to connections and send settings to the
+	 * parties.
+	 * <p>
+	 * This is 'protected' to allow junit testing, this code is not a 'public'
+	 * part of the interface.
+	 * 
+	 * @throws ProtocolException if a party does not follow the protocol
+	 */
+	protected synchronized void setupParties() throws ProtocolException {
+		for (ProtocolToPartyConn conn : conns) {
+			conn.addListener(action -> actionRequest(conn, action));
+		}
+
+		for (ProtocolToPartyConn connection : conns) {
+			try {
+				sendSettings(connection);
+			} catch (IOException e) {
+				throw new ProtocolException("Failed to initialize",
+						connection.getParty(), e);
+			}
+		}
+
+	}
+
+	/**
+	 * Inform a party about its settings
+	 * 
+	 * @param connection
+	 * @throws IOException if party got disconnected
+	 */
+	private synchronized void sendSettings(ProtocolToPartyConn connection)
+			throws IOException {
+		PartyId partyid = connection.getParty();
+		ProfileRef profile = state.getPartyProfiles().get(partyid).getProfile();
+		Parameters params = state.getPartyProfiles().get(partyid).getParty()
+				.getParameters();
+		if (profile == null) {
+			throw new IllegalArgumentException(
+					"Missing profile for party " + connection.getReference());
+		}
+		connection.send(new Settings(connection.getParty(), profile, getRef(),
+				state.getProgress(), params));
+	}
+
+	/**
+	 * Set state to proper deadline. Starts the timer tasks. This tasks triggers
+	 * a call to handleError when the session times out.
+	 */
+	private synchronized void setDeadline() {
+		long now = System.currentTimeMillis();
+		Deadline deadline = state.getSettings().getDeadline();
+		setState(state.with(ProgressFactory.create(deadline, now)));
+		deadlinetimer = new Timer();
+		TimerTask task = new TimerTask() {
+			@Override
+			public void run() {
+				if (!state.isFinal(System.currentTimeMillis())) {
+					log.log(Level.SEVERE,
+							"BUG. Deadline timer has triggered but state is not final");
+				}
+				log.log(Level.WARNING,
+						"SAOP deadline reached. Terminating session.");
+				finish();
+			}
+		};
+		// set timer TIME_MARGIN after real deadline to ensure we're not too
+		// early
+		deadlinetimer.schedule(task, deadline.getDuration() + TIME_MARGIN);
+		log.log(Level.INFO, "SAOP deadline set to "
+				+ new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
+						.format(new Date(System.currentTimeMillis()
+								+ deadline.getDuration() + TIME_MARGIN)));
+	}
+
+	/**
+	 * Update state to include the given error and finishes up the session.
+	 * 
+	 * @param message The message to attach to the error
+	 * @param party   the party where the error occured
+	 * @param e       the exception that occured.
+	 */
+	private synchronized void handleError(final String message,
+			final PartyId party, final Throwable e) {
+		if (e instanceof ProtocolException) {
+			setState(state.with((ProtocolException) e));
+		} else {
+			setState(state.with(new ProtocolException(message, party, e)));
+		}
+		log.log(Level.WARNING, "SAOP protocol intercepted error due to party "
+				+ party + ":" + message, e);
+	}
+
+	/**
+	 * Sets the new state. If the new state is final, the finish-up procedure is
+	 * executed.
+	 * 
+	 * @param newstate the new state.
+	 */
+	private synchronized void setState(LearnState newstate) {
+		long now = System.currentTimeMillis();
+		if (state.isFinal(now)) {
+			finish();
+			return;
+		}
+		this.state = newstate;
+		if (newstate.isFinal(now)) {
+			finish();
+		}
+	}
+
+	/**
+	 * Called when we reach final state. Cancels deadline timer. Send finished
+	 * info to all parties, notify current nego state as final and set
+	 * {@link #isFinishedInfoSent}. Double calls are automatically ignored.
+	 */
+	private synchronized void finish() {
+		if (deadlinetimer != null) {
+			deadlinetimer.cancel();
+			deadlinetimer = null;
+		}
+		if (!isFinishedInfoSent.compareAndSet(false, true))
+			return;
+		Inform finished = new Finished(state.getAgreements());
+		for (ProtocolToPartyConn conn : conns) {
+			sendFinish(conn, finished);
+		}
+		notifyListeners(new CurrentNegoState(state));
+	}
+
+	private void sendFinish(ProtocolToPartyConn connection, Inform finished) {
+		try {
+			connection.send(finished);
+			connection.close();
+		} catch (Exception e) {
+			log.log(Level.INFO, "Failed to send Finished to " + connection, e);
+		}
+	}
+}
Index: protocol/src/main/java/geniusweb/protocol/session/learn/LearnSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/learn/LearnSettings.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/session/learn/LearnSettings.java	(revision 31)
@@ -0,0 +1,181 @@
+package geniusweb.protocol.session.learn;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import geniusweb.actions.FileLocation;
+import geniusweb.deadline.Deadline;
+import geniusweb.protocol.session.SessionProtocol;
+import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.TeamInfo;
+import geniusweb.protocol.session.saop.SAOPSettings;
+import geniusweb.references.Parameters;
+import geniusweb.references.PartyWithProfile;
+import tudelft.utilities.logging.Reporter;
+
+/**
+ * Settings for learn protocol.
+ */
+public class LearnSettings implements SessionSettings {
+	private final List<TeamInfo> participants;
+	private final Deadline deadline;
+
+	private final static ObjectMapper jackson = new ObjectMapper();
+
+	/**
+	 * 
+	 * @param participants the list of {@link PartyWithProfile} in clockwise
+	 *                     order. There must be at least 2 to run the SAOP
+	 *                     protocol. But SAOP can be initialized with less, for
+	 *                     use in TournamentSettings.
+	 *                     <table>
+	 *                     <caption>Required parameters</caption>
+	 *                     <tr>
+	 *                     <td>persistentstate</td>
+	 *                     <td>a {@link FileLocation}, serialized as json eg
+	 *                     <code>"b29f3bf5-1dc4-499e-a676-7b2dbb864a03"</code> ,
+	 *                     where the party's persistant state is stored; this
+	 *                     must match the persistantstate used in earlier calls
+	 *                     for e.g. {@link SAOPSettings}.</td>
+	 *                     </tr>
+	 * 
+	 *                     <tr>
+	 *                     <td>negotiationdata</td>
+	 *                     <td>a List of {@link FileLocation}s, serialized as
+	 *                     json, matching the negotiationdata used in earlier
+	 *                     calls for eg {@link SAOPSettings}.</td>
+	 *                     </tr>
+	 *                     </table>
+	 * @param deadline     the deadline of the negotiation.
+	 */
+	@SuppressWarnings("unused")
+	@JsonCreator
+	public LearnSettings(
+			@JsonProperty("participants") List<TeamInfo> participants,
+			@JsonProperty("deadline") Deadline deadline) {
+		this.participants = participants;
+		this.deadline = deadline;
+		if (participants == null) {
+			throw new IllegalArgumentException("participants must not be null");
+		}
+		if (deadline == null) {
+			throw new IllegalArgumentException("deadline must not be null");
+		}
+
+		for (PartyWithProfile pwithp : getAllParties()) {
+			Parameters params = pwithp.getParty().getParameters();
+			Object statestr = params.get("persistentstate");
+			if (!(statestr instanceof String))
+				throw new IllegalArgumentException(
+						"persistentstate parameter containing UUID string is required, but found "
+								+ statestr);
+			// check that it contains a UUID. Don't store it, we don't need
+			// it.
+			UUID.fromString((String) statestr);
+
+			// and check the negotiationdata
+			Object negotiationsstr = params.get("negotiationdata");
+			if (!(negotiationsstr instanceof List))
+				throw new IllegalArgumentException(
+						"negotiationdata parameter containing a List is required");
+
+			for (Object negostr : (List) negotiationsstr) {
+				if (!(negostr instanceof String))
+					throw new IllegalArgumentException(
+							"The list in negotiationdata must contain UUID strings but found "
+									+ negostr);
+				UUID.fromString((String) negostr);
+			}
+		}
+	}
+
+	@Override
+	public Double getMaxRunTime() {
+		return deadline.getDuration() / 1000d;
+	}
+
+	@Override
+	public SessionProtocol getProtocol(Reporter logger) {
+		return new Learn(new LearnState(this), logger);
+	}
+
+	@Override
+	public List<TeamInfo> getTeams() {
+		return Collections.unmodifiableList(participants);
+	}
+
+	/**
+	 * @return the deadline for this negotiation
+	 */
+	public Deadline getDeadline() {
+		return deadline;
+	}
+
+	@Override
+	public List<PartyWithProfile> getAllParties() {
+		return participants.stream()
+				.map(particip -> particip.getAllParties().get(0))
+				.collect(Collectors.toList());
+	}
+
+	@Override
+	public Integer getTeamSize() {
+		return 1;
+	}
+
+	@Override
+	public String toString() {
+		return "LearnSettings" + participants + "," + deadline + "]";
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((deadline == null) ? 0 : deadline.hashCode());
+		result = prime * result
+				+ ((participants == null) ? 0 : participants.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		LearnSettings other = (LearnSettings) obj;
+		if (deadline == null) {
+			if (other.deadline != null)
+				return false;
+		} else if (!deadline.equals(other.deadline))
+			return false;
+		if (participants == null) {
+			if (other.participants != null)
+				return false;
+		} else if (!participants.equals(other.participants))
+			return false;
+		return true;
+	}
+
+	@Override
+	public SessionSettings with(TeamInfo team) {
+		if (team.getSize() != 1)
+			throw new IllegalArgumentException(
+					"Team must be size 1 but found " + team);
+		List<TeamInfo> newparts = new LinkedList<>(participants);
+		newparts.add(team);
+		return new LearnSettings(newparts, deadline);
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/learn/LearnState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/learn/LearnState.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/session/learn/LearnState.java	(revision 31)
@@ -0,0 +1,126 @@
+package geniusweb.protocol.session.learn;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import geniusweb.actions.Action;
+import geniusweb.actions.LearningDone;
+import geniusweb.actions.PartyId;
+import geniusweb.inform.Agreements;
+import geniusweb.progress.Progress;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.session.DefaultSessionState;
+import geniusweb.protocol.session.SessionResult;
+import geniusweb.references.PartyWithProfile;
+
+/**
+ * Stores the state for Learn protocol. Immutable.
+ */
+public class LearnState extends DefaultSessionState<LearnState, LearnSettings> {
+
+	/**
+	 * 
+	 * @param actions       the actions done by the parties
+	 * @param conns         the existing party connections. we assume ownership
+	 *                      of this so it should not be modified although
+	 *                      connections may of course break. If null, default
+	 *                      empty list is used. Can be less than 2 parties in
+	 *                      the first phases of the setup.
+	 * @param progress      the {@link Progress} line. can be null
+	 * @param settings      the {@link LearnSettings}
+	 * @param partyprofiles map with the {@link PartyWithProfile} for connected
+	 *                      parties. null is equivalent to an empty map.
+	 * @param e             the {@link ProtocolException}, or null if none
+	 *                      occurred.
+	 */
+	public LearnState(List<Action> actions, List<PartyId> conns,
+			Progress progress, LearnSettings settings,
+			Map<PartyId, PartyWithProfile> partyprofiles, ProtocolException e) {
+		super(actions, conns, progress, settings, partyprofiles, e);
+	}
+
+	/**
+	 * Creates the initial state from the given settings and progress=null
+	 * 
+	 * @param settings the {@link LearnSettings}
+	 */
+	public LearnState(LearnSettings settings) {
+		this(Collections.emptyList(), Collections.emptyList(), null, settings,
+				null, null);
+
+	}
+
+	@Override
+	public LearnState with(List<Action> actions, List<PartyId> conns,
+			Progress progr, LearnSettings settings,
+			Map<PartyId, PartyWithProfile> partyprofiles, ProtocolException e) {
+		return new LearnState(actions, conns, progr, settings, partyprofiles,
+				e);
+	}
+
+	@Override
+	public Agreements getAgreements() {
+		return new Agreements();
+	}
+
+	@Override
+	public SessionResult getResult() {
+		return new SessionResult(getPartyProfiles(), getAgreements(),
+				Collections.emptyMap(), null);
+	}
+
+	@Override
+	public boolean isFinal(long currentTimeMs) {
+		Set<PartyId> done = getDoneLearning();
+		// if done is empty we're probably still starting up.
+		return super.isFinal(currentTimeMs)
+				|| (!done.isEmpty() && done.containsAll(getConnections()));
+	}
+
+	/**
+	 * 
+	 * @param e the error that occured
+	 * @return a new state with the error set.
+	 */
+	@Override
+	public LearnState with(ProtocolException e) {
+		return new LearnState(getActions(), getConnections(), getProgress(),
+				getSettings(), getPartyProfiles(), e);
+	}
+
+	@Override
+	public LearnSettings getSettings() {
+		return super.getSettings();
+	}
+
+	/**
+	 * 
+	 * @param actor  the known real actor that did this action
+	 * @param action an {@link Action}
+	 * @return null if action seems ok, or message explaining why not.
+	 */
+	@Override
+	public String checkAction(PartyId actor, Action action) {
+		String msg = super.checkAction(actor, action);
+		if (msg != null)
+			return msg;
+		if (getDoneLearning().contains(actor))
+			return "actor already was done learning";
+		if (!(action instanceof LearningDone))
+			return "Action " + action + " is not allowed";
+		return null;
+	}
+
+	/**
+	 * 
+	 * @return set of parties that reported they are done with learning.
+	 */
+	private Set<PartyId> getDoneLearning() {
+		return getActions().stream().map(party -> party.getActor())
+				.collect(Collectors.toSet());
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/mopac/MOPAC.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/mopac/MOPAC.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/mopac/MOPAC.java	(revision 31)
@@ -46,9 +46,9 @@
  * specified initially in the settings do participate (no late joining in)
  * 
- * <h1>parameter</h1> MOPAC parties can receive a parameter <code>power</code>
+ * <h2>parameter</h2> MOPAC parties can receive a parameter <code>power</code>
  * containing a Integer. This parameter is picked up by this protocol, for
  * handling the agreement extraction from the offers. Power=1 for parties that
  * do not have the power parameter.
- * <h1>Protocol steps</h1>
+ * <h2>Protocol steps</h2>
  * 
  * <ol>
Index: protocol/src/main/java/geniusweb/protocol/session/mopac2/MOPAC2.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/mopac2/MOPAC2.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/mopac2/MOPAC2.java	(revision 31)
@@ -46,9 +46,9 @@
  * specified initially in the settings do participate (no late joining in)
  * 
- * <h1>parameter</h1> MOPAC parties can receive a parameter <code>power</code>
+ * <h2>parameter</h2> MOPAC parties can receive a parameter <code>power</code>
  * containing a Integer. This parameter is picked up by this protocol, for
  * handling the agreement extraction from the offers. Power=1 for parties that
  * do not have the power parameter.
- * <h1>Protocol steps</h1>
+ * <h2>Protocol steps</h2>
  * 
  * <ol>
Index: protocol/src/main/java/geniusweb/protocol/session/saop/SAOP.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/saop/SAOP.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/saop/SAOP.java	(revision 31)
@@ -4,4 +4,5 @@
 import java.sql.Date;
 import java.text.SimpleDateFormat;
+import java.util.Collections;
 import java.util.List;
 import java.util.Timer;
@@ -25,4 +26,5 @@
 import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
 import geniusweb.protocol.partyconnection.ProtocolToPartyConnFactory;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
 import geniusweb.protocol.session.SessionProtocol;
 import geniusweb.protocol.session.SessionSettings;
@@ -72,15 +74,13 @@
 	private final Reporter log;
 
+	// these 4 variables are why protocols are so complex.
 	private SAOPState state = null; // mutable!
 	private volatile AtomicBoolean isFinishedInfoSent = new AtomicBoolean(
 			false);
 	private volatile Timer deadlinetimer = null;
-
-	/**
-	 * 
-	 * @param state  normally the initial state coming from SAOPSettings
-	 * @param logger the Reporter to log to
-	 */
-	public SAOP(SAOPState state, Reporter logger) {
+	private volatile ProtocolToPartyConnections conns;
+
+	public SAOP(SAOPState state, Reporter logger,
+			ProtocolToPartyConnections connects) {
 		if (state == null) {
 			throw new NullPointerException("state must be not null");
@@ -92,4 +92,15 @@
 		this.log = logger;
 		this.state = state;
+		this.conns = connects;
+	}
+
+	/**
+	 * 
+	 * @param state  normally the initial state coming from SAOPSettings
+	 * @param logger the Reporter to log to
+	 */
+	public SAOP(SAOPState state, Reporter logger) {
+		this(state, logger,
+				new ProtocolToPartyConnections(Collections.emptyList()));
 	}
 
@@ -175,5 +186,9 @@
 		}
 		for (int i = 0; i < participants.size(); i++) {
-			setState(this.state.with(connections.get(i), participants.get(i)));
+			// now we bookkeep the connections ourselves,
+			// and update the state to keep in sync.
+			conns = conns.with(connections.get(i));
+			setState(this.state.with(connections.get(i).getParty(),
+					participants.get(i)));
 		}
 	}
@@ -219,9 +234,9 @@
 	 */
 	protected synchronized void setupParties() throws ProtocolException {
-		for (ProtocolToPartyConn conn : state.getConnections()) {
+		for (ProtocolToPartyConn conn : conns) {
 			conn.addListener(action -> actionRequest(conn, action));
 		}
 
-		for (ProtocolToPartyConn connection : state.getConnections()) {
+		for (ProtocolToPartyConn connection : conns) {
 			try {
 				sendSettings(connection);
@@ -282,5 +297,5 @@
 			}
 			// FIXME? this ignores possible broadcast errors
-			state.getConnections().broadcast(new ActionDone(action));
+			conns.broadcast(new ActionDone(action));
 			setState(state.with(partyconn.getParty(), action));
 			if (!state.isFinal(System.currentTimeMillis()))
@@ -298,8 +313,8 @@
 	 * @throws IOException
 	 */
-	private synchronized void nextTurn() {
+	synchronized void nextTurn() {
 		PartyId party = state.getNextActor();
 		try {
-			state.getConnections().get(party).send(new YourTurn());
+			conns.get(party).send(new YourTurn());
 		} catch (IOException e) {
 			handleError("failed to send YourTurn", party, e);
@@ -318,7 +333,9 @@
 		if (e instanceof ProtocolException) {
 			setState(state.with((ProtocolException) e));
+
 		} else {
 			setState(state.with(new ProtocolException(message, party, e)));
 		}
+		setState(state.withoutParty(party));
 		log.log(Level.WARNING, "SAOP protocol intercepted error due to party "
 				+ party + ":" + message, e);
@@ -356,5 +373,5 @@
 			return;
 		Inform finished = new Finished(state.getAgreements());
-		for (ProtocolToPartyConn conn : state.getConnections()) {
+		for (ProtocolToPartyConn conn : conns) {
 			sendFinish(conn, finished);
 		}
Index: protocol/src/main/java/geniusweb/protocol/session/saop/SAOPSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/saop/SAOPSettings.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/saop/SAOPSettings.java	(revision 31)
@@ -119,6 +119,5 @@
 			throw new IllegalArgumentException(
 					"Team must be size 1 but found " + team);
-		List<TeamInfo> newparts = new LinkedList<>(
-				participants);
+		List<TeamInfo> newparts = new LinkedList<>(participants);
 		newparts.add(team);
 		return new SAOPSettings(newparts, deadline);
Index: protocol/src/main/java/geniusweb/protocol/session/saop/SAOPState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/saop/SAOPState.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/session/saop/SAOPState.java	(revision 31)
@@ -2,10 +2,7 @@
 
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
 
 import geniusweb.actions.Accept;
@@ -19,9 +16,6 @@
 import geniusweb.progress.ProgressRounds;
 import geniusweb.protocol.ProtocolException;
-import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
-import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
 import geniusweb.protocol.session.DefaultSessionState;
 import geniusweb.protocol.session.SessionResult;
-import geniusweb.protocol.session.SessionSettings;
 import geniusweb.references.PartyWithProfile;
 
@@ -29,14 +23,13 @@
  * Immutable.
  */
-public class SAOPState extends DefaultSessionState {
+public class SAOPState extends DefaultSessionState<SAOPState, SAOPSettings> {
 
 	/**
 	 * 
 	 * @param actions       the actions done by the parties
-	 * @param conns         the existing party connections. we assume ownership
-	 *                      of this so it should not be modified although
-	 *                      connections may of course break. If null, default
+	 * @param conns         the existing party connections. If null, default
 	 *                      empty list is used. Can be less than 2 parties in
-	 *                      the first phases of the setup.
+	 *                      the first phases of the setup or after parties
+	 *                      disconnected.
 	 * @param progress      the {@link Progress} line. can be null
 	 * @param settings      the {@link SAOPSettings}
@@ -46,8 +39,15 @@
 	 *                      occurred.
 	 */
-	public SAOPState(List<Action> actions, ProtocolToPartyConnections conns,
-			Progress progress, SessionSettings settings,
+	public SAOPState(List<Action> actions, List<PartyId> conns,
+			Progress progress, SAOPSettings settings,
 			Map<PartyId, PartyWithProfile> partyprofiles, ProtocolException e) {
 		super(actions, conns, progress, settings, partyprofiles, e);
+	}
+
+	@Override
+	public SAOPState with(List<Action> actions, List<PartyId> conns,
+			Progress progr, SAOPSettings settings,
+			Map<PartyId, PartyWithProfile> partyprofiles, ProtocolException e) {
+		return new SAOPState(actions, conns, progr, settings, partyprofiles, e);
 	}
 
@@ -58,57 +58,7 @@
 	 */
 	public SAOPState(SAOPSettings settings) {
-		this(Collections.emptyList(),
-				new ProtocolToPartyConnections(Collections.emptyList()), null,
-				settings, null, null);
+		this(Collections.emptyList(), Collections.emptyList(), null, settings,
+				null, null);
 
-	}
-
-	/**
-	 * 
-	 * @param connection   the new {@link ProtocolToPartyConn}
-	 * @param partyprofile the {@link PartyWithProfile} that is associated with
-	 *                     this state
-	 * @return new SessionState with the new connection added. This call ignores
-	 *         the progress (does not check isFinal) because SAOP uses this
-	 *         during the setup where the deadline is not yet relevant. The
-	 *         connection should be informed about the partyprofile but the
-	 *         state currently indicate if that already happened or not.
-	 */
-	public SAOPState with(ProtocolToPartyConn connection,
-			PartyWithProfile partyprofile) {
-		// Only called from the SAOP initialization phase.
-		ProtocolToPartyConnections newconns = getConnections().with(connection);
-		Map<PartyId, PartyWithProfile> newprofiles = new HashMap<>(
-				getPartyProfiles());
-		newprofiles.put(connection.getParty(), partyprofile);
-		return new SAOPState(getActions(), newconns, getProgress(),
-				getSettings(), newprofiles, null);
-	}
-
-	/**
-	 * 
-	 * @param e the error that occured
-	 * @return a new state with the error set.
-	 */
-	public SAOPState with(ProtocolException e) {
-		return new SAOPState(getActions(), getConnections(), getProgress(),
-				getSettings(), getPartyProfiles(), e);
-	}
-
-	/**
-	 * Sets the progress for this session. Can be set only if progress=null.
-	 * 
-	 * @param newprogress the new progress
-	 * @return new SAOPState with the progress set
-	 */
-	public SAOPState with(Progress newprogress) {
-		if (newprogress == null) {
-			throw new IllegalArgumentException("newprogress must be not null");
-		}
-		if (getProgress() != null) {
-			return this;
-		}
-		return new SAOPState(getActions(), getConnections(), newprogress,
-				getSettings(), getPartyProfiles(), getError());
 	}
 
@@ -131,5 +81,6 @@
 						&& bid.equals(((Accept) act).getBid()));
 		if (allaccept)
-			agree = agree.with(new Agreements(bid, getParties()));
+			agree = agree
+					.with(new Agreements(bid, new HashSet<>(getParties())));
 		return agree;
 	}
@@ -139,7 +90,6 @@
 	 * @return all currently connected parties.
 	 */
-	private Set<PartyId> getParties() {
-		return getConnections().stream().map(conn -> conn.getParty())
-				.collect(Collectors.toSet());
+	public List<PartyId> getParties() {
+		return getConnections();
 	}
 
@@ -153,59 +103,33 @@
 
 	@Override
-	public SAOPSettings getSettings() {
-		return (SAOPSettings) super.getSettings();
+	public SAOPState with(PartyId actor, Action action) {
+		return super.with(actor, action).with(advanceProgress());
 	}
 
-	/**
-	 * @param actor  the actor that did this action. Can be used to check if
-	 *               action is valid. NOTICE caller has to make sure the current
-	 *               state is not final.
-	 * @param action the action that was proposed by actor.
-	 * @return new SAOPState with the action added as last action.
-	 */
+	@Override
+	public String checkAction(PartyId actor, Action action) {
+		String msg = super.checkAction(actor, action);
+		if (msg != null)
+			return msg;
 
-	public SAOPState with(PartyId actor, Action action) {
-
-		if (actor == null) { // this is a bug
-			throw new IllegalArgumentException("actor must not be null");
-		}
-		if (!actor.equals(getNextActor())) {
-			throw new IllegalArgumentException("Party does not have the turn ");
-
-		}
-		if (action == null) {
-			throw new IllegalArgumentException("action is null");
-		}
-		if (!actor.equals(action.getActor())) {
-			throw new IllegalArgumentException(
-					"act contains wrong credentials: " + action);
-		}
+		if (!actor.equals(getNextActor()))
+			return "Party does not have the turn ";
 
 		// check protocol is followed for specific actions
 		if (action instanceof Accept) {
 			Bid bid = getLastBid();
-			if (bid == null) {
-				throw new IllegalArgumentException(
-						"Accept without a recent offer");
-			}
-			if (!bid.equals(((Accept) action).getBid())) {
-				throw new IllegalArgumentException(
-						"Party accepts a bid differing from the last offer ="
-								+ bid + ", action=" + action + ")");
-			}
+			if (bid == null)
+				return "Accept without a recent offer";
+
+			if (!bid.equals(((Accept) action).getBid()))
+				return "Party accepts a bid differing from the last offer ="
+						+ bid + ", action=" + action + ")";
+			return null;
 		} else if (action instanceof Offer) {
-			// offer is always fine. We don't check if bid is actually in domain
-			// or complete etc. We would need domain for that
+			return null;
 		} else if (action instanceof EndNegotiation) {
-			// just act, we will get into the final state then.
-		} else {
-			throw new IllegalArgumentException(
-					"Action " + action + " is not allowed in SAOP");
+			return null;
 		}
-
-		List<Action> newactions = new LinkedList<>(getActions());
-		newactions.add(action);
-		return new SAOPState(newactions, getConnections(), advanceProgress(),
-				getSettings(), getPartyProfiles(), null);
+		return "Action " + action + " is not allowed in SAOP";
 	}
 
@@ -232,17 +156,16 @@
 	 * 
 	 * @return the next actor in the current state. Assumes 1 action per actor
-	 *         every time.
+	 *         every time. NOTE if party disconnects this would jump wildly but
+	 *         nego stops then anyway
 	 */
 	protected PartyId getNextActor() {
 		return getConnections()
-				.get(getActions().size() % getConnections().size()).getParty();
+				.get(getActions().size() % getConnections().size());
 	}
 
 	/**
-	 * 
-	 * @param progress
 	 * @return new progress state
 	 */
-	private Progress advanceProgress() {
+	public Progress advanceProgress() {
 		Progress newprogress = getProgress();
 		if (newprogress instanceof ProgressRounds && isLastActor()) {
Index: protocol/src/main/java/geniusweb/protocol/tournament/ProfileList.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/ProfileList.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/tournament/ProfileList.java	(revision 31)
@@ -0,0 +1,68 @@
+package geniusweb.protocol.tournament;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import geniusweb.references.ProfileRef;
+
+/**
+ * Contains a list of profiles, one for each {@link Team} member.
+ */
+@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
+public class ProfileList {
+	@JsonValue
+	private final List<ProfileRef> profiles; // field to ensure immutability
+
+	/**
+	 * 
+	 * @param ps a list of {@link ProfileRef}s. The length and order is
+	 *           important as each will be matched with the {@link Team} member
+	 *           at the same index.
+	 */
+	@JsonCreator
+	public ProfileList(List<ProfileRef> ps) {
+		this.profiles = new ArrayList<>(ps);
+	}
+
+	public List<ProfileRef> getProfiles() {
+		return Collections.unmodifiableList(profiles);
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((profiles == null) ? 0 : profiles.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		ProfileList other = (ProfileList) obj;
+		if (profiles == null) {
+			if (other.profiles != null)
+				return false;
+		} else if (!profiles.equals(other.profiles))
+			return false;
+		return true;
+	}
+
+	public String toString() {
+		return profiles.toString();
+	}
+}
Index: protocol/src/main/java/geniusweb/protocol/tournament/Team.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/Team.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/tournament/Team.java	(revision 31)
@@ -0,0 +1,74 @@
+package geniusweb.protocol.tournament;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import geniusweb.references.PartyWithParameters;
+
+/**
+ * A team is a group of parties, each with their own parameters. Each will be
+ * coupled to a {@link ProfileList} in the generation phase of the protocol
+ *
+ */
+@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
+public class Team {
+	@JsonValue
+	private final List<PartyWithParameters> parties; // ensure immutability
+
+	/**
+	 * 
+	 * @param ps a list of {@link PartyWithParameters}. Each party is always
+	 *           associated with the given parameters. The order of them is
+	 *           important as it will be matched with the order in the
+	 *           {@link ProfileList}.
+	 */
+	@JsonCreator
+	public Team(List<PartyWithParameters> ps) {
+		this.parties = new ArrayList<PartyWithParameters>(ps);
+	}
+
+	/**
+	 * 
+	 * @return list of all team {@link PartyWithParameters}.
+	 */
+	public List<PartyWithParameters> getParties() {
+		return Collections.unmodifiableList(parties);
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((parties == null) ? 0 : parties.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		Team other = (Team) obj;
+		if (parties == null) {
+			if (other.parties != null)
+				return false;
+		} else if (!parties.equals(other.parties))
+			return false;
+		return true;
+	}
+
+	public String toString() {
+		return parties.toString();
+	}
+}
Index: protocol/src/main/java/geniusweb/protocol/tournament/TournamentSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/TournamentSettings.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/tournament/TournamentSettings.java	(revision 31)
@@ -5,4 +5,5 @@
 import geniusweb.protocol.NegoSettings;
 import geniusweb.protocol.tournament.allpermutations.AllPermutationsSettings;
+import geniusweb.protocol.tournament.allpermutationslearn.AllPermutationsLearnSettings;
 import tudelft.utilities.logging.Reporter;
 
@@ -10,5 +11,6 @@
  * Interface for tournament settings
  */
-@JsonSubTypes({ @JsonSubTypes.Type(value = AllPermutationsSettings.class) })
+@JsonSubTypes({ @JsonSubTypes.Type(value = AllPermutationsSettings.class),
+		@JsonSubTypes.Type(value = AllPermutationsLearnSettings.class) })
 public interface TournamentSettings extends NegoSettings {
 	@Override
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsSettings.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsSettings.java	(revision 31)
@@ -14,4 +14,6 @@
 import geniusweb.protocol.session.saop.SAOPSettings;
 import geniusweb.protocol.session.shaop.SHAOPSettings;
+import geniusweb.protocol.tournament.ProfileList;
+import geniusweb.protocol.tournament.Team;
 import geniusweb.protocol.tournament.TournamentProtocol;
 import geniusweb.protocol.tournament.TournamentSettings;
@@ -59,10 +61,10 @@
 public class AllPermutationsSettings implements TournamentSettings {
 
-	private final List<Team> teams;
-	private final List<ProfileList> profileslists;
-	private final boolean reuseTeams;
-	private final int teamsPerSession;
-	private final SessionSettings sessionsettings;
-	private final int numberTournaments;
+	protected final List<Team> teams;
+	protected final List<ProfileList> profileslists;
+	protected final boolean reuseTeams;
+	protected final int teamsPerSession;
+	protected final SessionSettings sessionsettings;
+	protected final int numberTournaments;
 
 	/**
@@ -81,7 +83,6 @@
 	 * @param teamsPerSession number of parties per session, must be at least 2.
 	 * @param sesettings      The generic {@link SessionSettings}.
-	 *                        {@link SessionSettings#with(TeamInfo)}
-	 *                        will be used to add the required
-	 *                        {@link TeamInfo}s
+	 *                        {@link SessionSettings#with(TeamInfo)} will be
+	 *                        used to add the required {@link TeamInfo}s
 	 * @param nTournaments    the number of times the tournament should be run.
 	 */
@@ -158,7 +159,11 @@
 	@Override
 	public String toString() {
-		return "AllPermutationsSettings[" + teams + "," + reuseTeams + ","
-				+ profileslists + "," + teamsPerSession + "," + sessionsettings
-				+ "," + numberTournaments + "]";
+		return "AllPermutationsSettings[" + contentString() + "]";
+	}
+
+	protected String contentString() {
+		return teams + "," + reuseTeams + "," + profileslists + ","
+				+ teamsPerSession + "," + sessionsettings + ","
+				+ numberTournaments;
 	}
 
@@ -216,5 +221,5 @@
 	 * @return {@link SessionSettings}
 	 */
-	private SessionSettings createSetting(
+	protected SessionSettings createSetting(
 			ImmutableList<TeamInfo> partyproflist) {
 		SessionSettings settings = sessionsettings;
@@ -227,14 +232,14 @@
 	/**
 	 * 
-	 * @param parties
-	 * @param profiles
-	 * @param n                    number of items to draw. Assumed >= 1.
+	 * @param parties              the parties in the tournament
+	 * @param profiles             the profiles in the tournament
+	 * @param n                    number of items to draw. Assumed &ge;1.
 	 * @param drawPartyWithPutback if parties can be drawn multiple times
 	 * @return list of sessionsets where each sessionset contains settings for a
-	 *         session: a list with {@link TeamInfo}s. The
-	 *         sessionsets are made by making all combinations of parties and
-	 *         profiles. Profiles are drawn with replace.
-	 */
-	private ImmutableList<ImmutableList<TeamInfo>> getParticipants(
+	 *         session: a list with {@link TeamInfo}s. The sessionsets are made
+	 *         by making all combinations of parties and profiles. Profiles are
+	 *         drawn with replace.
+	 */
+	protected ImmutableList<ImmutableList<TeamInfo>> getParticipants(
 			ImmutableList<Team> parties, ImmutableList<ProfileList> profiles,
 			int n, boolean drawPartyWithPutback) {
@@ -257,7 +262,6 @@
 		MapList<Tuple<ImmutableList<Team>, ImmutableList<ProfileList>>, ImmutableList<TeamInfo>> tournamentsettings = new MapList<Tuple<ImmutableList<Team>, ImmutableList<ProfileList>>, ImmutableList<TeamInfo>>(
 				tuple -> teamlist(tuple), tuples);
-		return new Repeat<ImmutableList<TeamInfo>>(
-				tournamentsettings, BigInteger.valueOf(numberTournaments),
-				true);
+		return new Repeat<ImmutableList<TeamInfo>>(tournamentsettings,
+				BigInteger.valueOf(numberTournaments), true);
 	}
 
@@ -273,6 +277,5 @@
 		BiFunction<Team, ProfileList, TeamInfo> makeparty = new BiFunction<Team, ProfileList, TeamInfo>() {
 			@Override
-			public TeamInfo apply(Team team,
-					ProfileList profilelist) {
+			public TeamInfo apply(Team team, ProfileList profilelist) {
 				List<PartyWithParameters> parties = team.getParties();
 				List<ProfileRef> profiles = profilelist.getProfiles();
@@ -291,6 +294,6 @@
 
 		};
-		return new MapThreadList<TeamInfo, Team, ProfileList>(
-				makeparty, tuple.get1(), tuple.get2());
+		return new MapThreadList<TeamInfo, Team, ProfileList>(makeparty,
+				tuple.get1(), tuple.get2());
 	}
 
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsState.java	(revision 30)
+++ protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsState.java	(revision 31)
@@ -15,8 +15,8 @@
 public class AllPermutationsState implements TournamentState {
 
-	private final AllPermutationsSettings toursettings; // original settings
-	private final ImmutableList<SessionSettings> sessionsettings;
+	protected final AllPermutationsSettings toursettings; // original settings
+	protected final ImmutableList<SessionSettings> sessionsettings;
 	// limited to MAX_INT elements. But we need to store results anyway
-	private final List<SessionResult> results;
+	protected final List<SessionResult> results;
 
 	public AllPermutationsState(AllPermutationsSettings toursettings,
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/ProfileList.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/ProfileList.java	(revision 30)
+++ 	(revision )
@@ -1,68 +1,0 @@
-package geniusweb.protocol.tournament.allpermutations;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.JsonValue;
-
-import geniusweb.references.ProfileRef;
-
-/**
- * Contains a list of profiles, one for each {@link Team} member.
- */
-@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE)
-@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
-public class ProfileList {
-	@JsonValue
-	private final List<ProfileRef> profiles; // field to ensure immutability
-
-	/**
-	 * 
-	 * @param ps a list of {@link ProfileRef}s. The length and order is
-	 *           important as each will be matched with the {@link Team} member
-	 *           at the same index.
-	 */
-	@JsonCreator
-	public ProfileList(List<ProfileRef> ps) {
-		this.profiles = new ArrayList<>(ps);
-	}
-
-	public List<ProfileRef> getProfiles() {
-		return Collections.unmodifiableList(profiles);
-	}
-
-	@Override
-	public int hashCode() {
-		final int prime = 31;
-		int result = 1;
-		result = prime * result
-				+ ((profiles == null) ? 0 : profiles.hashCode());
-		return result;
-	}
-
-	@Override
-	public boolean equals(Object obj) {
-		if (this == obj)
-			return true;
-		if (obj == null)
-			return false;
-		if (getClass() != obj.getClass())
-			return false;
-		ProfileList other = (ProfileList) obj;
-		if (profiles == null) {
-			if (other.profiles != null)
-				return false;
-		} else if (!profiles.equals(other.profiles))
-			return false;
-		return true;
-	}
-
-	public String toString() {
-		return profiles.toString();
-	}
-}
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/Team.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/Team.java	(revision 30)
+++ 	(revision )
@@ -1,74 +1,0 @@
-package geniusweb.protocol.tournament.allpermutations;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import com.fasterxml.jackson.annotation.JsonValue;
-
-import geniusweb.references.PartyWithParameters;
-
-/**
- * A team is a group of parties, each with their own parameters. Each will be
- * coupled to a {@link ProfileList} in the generation phase of the protocol
- *
- */
-@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE)
-@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
-public class Team {
-	@JsonValue
-	private final List<PartyWithParameters> parties; // ensure immutability
-
-	/**
-	 * 
-	 * @param ps a list of {@link PartyWithParameters}. Each party is always
-	 *           associated with the given parameters. The order of them is
-	 *           important as it will be matched with the order in the
-	 *           {@link ProfileList}.
-	 */
-	@JsonCreator
-	public Team(List<PartyWithParameters> ps) {
-		this.parties = new ArrayList<PartyWithParameters>(ps);
-	}
-
-	/**
-	 * 
-	 * @return list of all team {@link PartyWithParameters}.
-	 */
-	public List<PartyWithParameters> getParties() {
-		return Collections.unmodifiableList(parties);
-	}
-
-	@Override
-	public int hashCode() {
-		final int prime = 31;
-		int result = 1;
-		result = prime * result + ((parties == null) ? 0 : parties.hashCode());
-		return result;
-	}
-
-	@Override
-	public boolean equals(Object obj) {
-		if (this == obj)
-			return true;
-		if (obj == null)
-			return false;
-		if (getClass() != obj.getClass())
-			return false;
-		Team other = (Team) obj;
-		if (parties == null) {
-			if (other.parties != null)
-				return false;
-		} else if (!parties.equals(other.parties))
-			return false;
-		return true;
-	}
-
-	public String toString() {
-		return parties.toString();
-	}
-}
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnProtocol.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnProtocol.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnProtocol.java	(revision 31)
@@ -0,0 +1,48 @@
+package geniusweb.protocol.tournament.allpermutationslearn;
+
+import geniusweb.protocol.tournament.TournamentState;
+import geniusweb.protocol.tournament.allpermutations.AllPermutationsProtocol;
+import geniusweb.protocol.tournament.allpermutations.AllPermutationsState;
+import geniusweb.references.ProtocolRef;
+import tudelft.utilities.logging.Reporter;
+
+/**
+ * This protocol creates sequentially all permutations of available parties and
+ * profiles, See {@link AllPermutationsLearnSettings} for details. Only 1
+ * session is run at a time because managing multiple sessions is more complex
+ * to manage in the {@link TournamentState}
+ * 
+ */
+public class AllPermutationsLearnProtocol extends AllPermutationsProtocol {
+	private static final ProtocolRef APPLearn = new ProtocolRef("APPLearn");
+
+	/**
+	 * 
+	 * @param state  the {@link AllPermutationsState}
+	 * @param logger used for all logging, about the tournament, and the created
+	 *               the session and protocols.
+	 */
+	public AllPermutationsLearnProtocol(AllPermutationsLearnState state,
+			Reporter logger) {
+		super(state, logger);
+	}
+
+	@Override
+	public String getDescription() {
+		return "Runs all possible permutations of provided parties and profiles, "
+				+ "with the given number of parties per session. Sessions run one at a time. "
+				+ "In each session, all parties receive FileLocations in the 'negotiationdata' parameter."
+				+ "If reuseParties is set, then a party can occur more than 1 time in each session. "
+				+ "The sessionSettings determine the baseline settings for each session, eg the deadline "
+				+ "and the protocol to be used. The AllPermutationsProtocol supports all basic SessionProtocols. "
+				+ "parties and protocols already available in the settings will participate in every session. "
+				+ "After all sessions have been run, a Learn session is done where all parties receive their"
+				+ "previously stored FileLocations again.";
+	}
+
+	@Override
+	public ProtocolRef getRef() {
+		return APPLearn;
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnSettings.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnSettings.java	(revision 31)
@@ -0,0 +1,264 @@
+package geniusweb.protocol.tournament.allpermutationslearn;
+
+import java.math.BigInteger;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import geniusweb.actions.FileLocation;
+import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.TeamInfo;
+import geniusweb.protocol.session.learn.LearnSettings;
+import geniusweb.protocol.tournament.ProfileList;
+import geniusweb.protocol.tournament.Team;
+import geniusweb.protocol.tournament.TournamentProtocol;
+import geniusweb.protocol.tournament.allpermutations.AllPermutationsSettings;
+import geniusweb.references.Parameters;
+import geniusweb.references.PartyWithProfile;
+import geniusweb.references.ProfileRef;
+import tudelft.utilities.immutablelist.FixedList;
+import tudelft.utilities.immutablelist.ImmutableList;
+import tudelft.utilities.immutablelist.JoinedList;
+import tudelft.utilities.immutablelist.MapThreadList;
+import tudelft.utilities.logging.Reporter;
+
+/**
+ * Almost the same as AllPermutationsSettings.
+ * <p>
+ * The only additional field is the additional learnTime. This has a maximum to
+ * the maximum number of sessions, {@link #MAX_SESSIONS}. This is because we
+ * need to store all the {@link FileLocation}s explicitly.
+ * <p>
+ * Only the first team member is actually getting the learn time.
+ * <p>
+ * We assume that all parties have the proper persistentstate parameter already.
+ * This is assumed to be set in the {@link Team} settings
+ */
+public class AllPermutationsLearnSettings extends AllPermutationsSettings {
+
+	/**
+	 * MAX_SESSIONS is the maximum number of sessions in a tournament. It is
+	 * depending on a number of factors:
+	 * <ul>
+	 * <li>The memory use to store all UUIDs for all parties
+	 * <li>The MAX_MESSAGE_SIZE of a websocket message.
+	 * </ul>
+	 * The MAX_MESSAGE_SIZE of the partyserver currently is 20MB. A single UUID
+	 * is about 45 bytes including double quotes and commas
+	 * </ul>
+	 */
+	private final static int MAX_SESSIONS = 400000;
+	private final static ObjectMapper jackson = new ObjectMapper();
+
+	private final LearnSettings learnSettings;
+
+	/**
+	 * 
+	 * @param teams           a list of {@link Team}s. Must contain at least
+	 *                        {@link #teamsPerSession} elements. The
+	 *                        {@link #teamsPerSession} must match the protocol:
+	 *                        (SAOP:1, SHAOP:2)
+	 * @param reuseTeams      if true, we use PermutationsWithReturn, if false
+	 *                        we use PermutationsWithoutReturn to create the
+	 *                        teams.
+	 * @param plists          list of available {@link ProfileList}s to be
+	 *                        permutated over the teams. Each
+	 *                        {@link ProfileList} must contain
+	 *                        {@link #teamsPerSession} elements.
+	 * @param teamsPerSession number of parties per session, must be at least 2.
+	 * @param sesettings      The generic {@link SessionSettings}.
+	 *                        {@link SessionSettings#with(TeamInfo)} will be
+	 *                        used to add the required {@link TeamInfo}s. All
+	 *                        settings must contain a 'persistentstate'
+	 *                        parameter with a {@link FileLocation}.
+	 * @param nTournaments    the number of times the tournament should be run.
+	 * @param learnSet        the {@link LearnSettings} to be used for the
+	 *                        learning phase. The participants list should be
+	 *                        empty as this will be filled up from the teams
+	 *                        provided above. Only the deadline is used.
+	 */
+	@JsonCreator
+	public AllPermutationsLearnSettings(@JsonProperty("teams") List<Team> teams,
+			@JsonProperty("profileslists") List<ProfileList> plists,
+			@JsonProperty("reuseTeams") boolean reuseTeams,
+			@JsonProperty("teamsPerSession") int teamsPerSession,
+			@JsonProperty("sessionsettings") SessionSettings sesettings,
+			@JsonProperty("numberTournaments") int nTournaments,
+			@JsonProperty("learnSettings") LearnSettings learnSet) {
+		super(teams, plists, reuseTeams, teamsPerSession, sesettings,
+				nTournaments);
+		this.learnSettings = learnSet;
+	}
+
+	@Override
+	public TournamentProtocol getProtocol(Reporter logger) {
+		AllPermutationsLearnState state = new AllPermutationsLearnState(this,
+				permutations(), Collections.emptyList());
+		return new AllPermutationsLearnProtocol(state, logger);
+	}
+
+	@Override
+	public Double getMaxRunTime() {
+		return super.permutations().size().doubleValue()
+				* (sessionsettings.getMaxRunTime() + 2d)
+				+ learnSettings.getMaxRunTime();
+	}
+
+	/**
+	 * @return {@link ImmutableList} containing all {@link SessionSettings} for
+	 *         all Sessions in the correct order.
+	 */
+	@Override
+	public ImmutableList<SessionSettings> permutations() {
+		ImmutableList<SessionSettings> allsessionsettings = super.permutations();
+		if (allsessionsettings.size()
+				.compareTo(BigInteger.valueOf(MAX_SESSIONS)) > 0)
+			throw new IllegalArgumentException(
+					"learn-tournament exceeds max number of sessions  "
+							+ MAX_SESSIONS);
+
+		FixedList<FixedList<FileLocation>> filelocs = createFileLocations(
+				allsessionsettings.size().longValue());
+
+		ImmutableList<ImmutableList<TeamInfo>> partylistlist;
+		ImmutableList<Team> partieslist = new FixedList<Team>(teams);
+		ImmutableList<ProfileList> profileslist = new FixedList<ProfileList>(
+				profileslists);
+		partylistlist = getParticipants(partieslist, profileslist,
+				teamsPerSession, reuseTeams);
+		ImmutableList<SessionSettings> settings = new MapThreadList<>(
+				(parties, flocs) -> createSetting(parties, flocs),
+				partylistlist, filelocs);
+
+		return new JoinedList<SessionSettings>(settings,
+				getLearnSessions(filelocs, partylistlist));
+	}
+
+	@Override
+	public String toString() {
+		return "AllPermutationsLearnSettings[" + contentString() + ","
+				+ learnSettings + "]";
+	}
+
+	/**
+	 * 
+	 * @param numsessions the number of sessions in the tournament
+	 * @return A list of list of {@link FileLocation}s. The outer list has one
+	 *         entry for each session, the inner list has one
+	 *         {@link FileLocation} per party.
+	 */
+	private FixedList<FixedList<FileLocation>> createFileLocations(
+			long numsessions) {
+		// like before, but all sessions need a new negotiationdata
+		List<FixedList<FileLocation>> filelocations = new ArrayList<>();
+		for (long sessionnr = 0; sessionnr < numsessions; sessionnr++) {
+			List<FileLocation> locs = new ArrayList<>();
+			for (int teamnr = 0; teamnr < teamsPerSession; teamnr++) {
+				locs.add(new FileLocation());
+			}
+			filelocations.add(new FixedList<>(locs));
+		}
+		FixedList<FixedList<FileLocation>> filelocs = new FixedList<>(
+				filelocations);
+		return filelocs;
+	}
+
+	/**
+	 * @param allsessionsettings the 'traditional session settings.
+	 * @param filelocs           the {@link FileLocation}s for each session, for
+	 *                           each party
+	 * @param partylistlist      the lists of all teams for the sessions, with
+	 *                           unaltered parameters matching the
+	 *                           {@link #teams} list
+	 * @return the final sessions, doing learning for all parties. In this
+	 *         session, the number of parties may be larger than
+	 *         {@link #teamsPerSession}. Learning may be spread over multiple
+	 *         sessions, although we don't do this currently.
+	 */
+	private ImmutableList<SessionSettings> getLearnSessions(
+			FixedList<FixedList<FileLocation>> filelocs,
+			ImmutableList<ImmutableList<TeamInfo>> partylistlist) {
+		try {
+			ProfileRef NOPROFILE = new ProfileRef("not://aprofile");
+
+			SessionSettings session = learnSettings;
+			// add all the teams, with updated parameters, to the session
+			for (Team team : teams) {
+				List<FileLocation> files = getNegoData(team, filelocs,
+						partylistlist);
+				// Tricky: correct parameter is list of UUID strings
+				List<String> filesstrings = files.stream()
+						.map(file -> file.getUUIDString())
+						.collect(Collectors.toList());
+				Parameters parameters = new Parameters().with("negotiationdata",
+						filesstrings);
+				// add all the FileLocations and NOPROFILE to all team members
+				List<PartyWithProfile> teaminfo = team.getParties().stream()
+						.map(pwp -> new PartyWithProfile(pwp.with(parameters),
+								NOPROFILE))
+						.collect(Collectors.toList());
+				session = session.with(new TeamInfo(teaminfo));
+			}
+
+			return new FixedList<SessionSettings>(Arrays.asList(session));
+		} catch (URISyntaxException e) {
+			throw new RuntimeException("Failed creating learn session", e);
+		}
+	}
+
+	/**
+	 * @param team     the team for which the {@link FileLocation}s are needed
+	 * @param filelocs the used {@link FileLocation}s for all sessions, all
+	 *                 teams.
+	 * @param TeamInfo the TeamInfo used for each session, team. Same dimensions
+	 *                 as filelocs
+	 * @return the list of FileLocations that were provided to the given team in
+	 *         the negotiation sessions.
+	 */
+	private List<FileLocation> getNegoData(Team team,
+			FixedList<FixedList<FileLocation>> filelocs,
+			ImmutableList<ImmutableList<TeamInfo>> partylistlist) {
+		List<FileLocation> locs = new ArrayList<>();
+		for (long sessionnr = 0; sessionnr < filelocs.size()
+				.longValue(); sessionnr++) {
+			for (int teamnr = 0; teamnr < teamsPerSession; teamnr++) {
+				if (team.equals(
+						partylistlist.get(sessionnr).get(teamnr).getTeam()))
+					locs.add(filelocs.get(sessionnr).get(teamnr));
+			}
+		}
+		return locs;
+
+	}
+
+	/**
+	 * 
+	 * @param partyproflist a list of {@link PartyWithProfile} settings. Now,
+	 *                      each party gets a new negotiationdata object in
+	 *                      every session.
+	 * @param locs          the FileLocations for the negotiationdata for all
+	 *                      teams
+	 * @return {@link SessionSettings}, with each party parameters extended with
+	 *         its {@link FileLocation}
+	 */
+	private SessionSettings createSetting(ImmutableList<TeamInfo> partyproflist,
+			ImmutableList<FileLocation> locs) {
+		SessionSettings settings = sessionsettings;
+		for (int teamnr = 0; teamnr < partyproflist.size()
+				.intValue(); teamnr++) {
+			Parameters extraparams = new Parameters().with("negotiationdata",
+					Arrays.asList(locs.get(teamnr)));
+			TeamInfo teaminfo = partyproflist.get(teamnr).with(extraparams);
+			settings = settings.with(teaminfo);
+		}
+		return settings;
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnState.java	(revision 31)
+++ protocol/src/main/java/geniusweb/protocol/tournament/allpermutationslearn/AllPermutationsLearnState.java	(revision 31)
@@ -0,0 +1,38 @@
+package geniusweb.protocol.tournament.allpermutationslearn;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import geniusweb.protocol.session.SessionResult;
+import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.tournament.allpermutations.AllPermutationsState;
+import tudelft.utilities.immutablelist.ImmutableList;
+
+/**
+ * A non-parallel simple implementation of all-permutations
+ */
+public class AllPermutationsLearnState extends AllPermutationsState {
+
+	public AllPermutationsLearnState(AllPermutationsLearnSettings toursettings,
+			ImmutableList<SessionSettings> settings,
+			List<SessionResult> results) {
+		super(toursettings, settings, results);
+	}
+
+	/**
+	 * 
+	 * @param result the next {@link SessionResult}
+	 * @return new state
+	 * @throws IllegalArgumentException if the result participants are not
+	 *                                  matching the expected participants
+	 */
+	@Override
+	public AllPermutationsLearnState with(SessionResult result) {
+		ArrayList<SessionResult> newresults = new ArrayList<>(results);
+		newresults.add(result);
+		return new AllPermutationsLearnState(
+				(AllPermutationsLearnSettings) toursettings, sessionsettings,
+				newresults);
+	}
+
+}
