Index: protocol/src/main/java/geniusweb/protocol/session/DefaultSessionState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/DefaultSessionState.java	(revision 9)
+++ protocol/src/main/java/geniusweb/protocol/session/DefaultSessionState.java	(revision 10)
@@ -64,9 +64,9 @@
 			Map<PartyId, PartyWithProfile> partyprofiles, ProtocolException e) {
 		if (partyprofiles == null) {
-			partyprofiles = Collections.EMPTY_MAP;
+			partyprofiles = Collections.emptyMap();
 		}
 		if (conns == null) {
 			this.connections = new ProtocolToPartyConnections(
-					Collections.EMPTY_LIST);
+					Collections.emptyList());
 		} else {
 			this.connections = conns;
@@ -97,5 +97,4 @@
 
 	/**
-	 * @return
 	 * @return map with {@link PartyWithProfile} for the parties. May be an
 	 *         incomplete map, as more parties with their profiles may be set
Index: protocol/src/main/java/geniusweb/protocol/session/SessionSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/SessionSettings.java	(revision 9)
+++ protocol/src/main/java/geniusweb/protocol/session/SessionSettings.java	(revision 10)
@@ -7,4 +7,5 @@
 import geniusweb.protocol.NegoSettings;
 import geniusweb.protocol.session.saop.SAOPSettings;
+import geniusweb.protocol.session.shaop.SHAOPSettings;
 import geniusweb.references.PartyWithProfile;
 import tudelft.utilities.logging.Reporter;
@@ -13,6 +14,6 @@
  * interface for settings for session protocols. Immutable.
  */
-@JsonSubTypes({ @JsonSubTypes.Type(value = SAOPSettings.class) })
-
+@JsonSubTypes({ @JsonSubTypes.Type(value = SAOPSettings.class),
+		@JsonSubTypes.Type(value = SHAOPSettings.class) })
 public interface SessionSettings extends NegoSettings {
 
@@ -22,22 +23,31 @@
 	 * @return list of {@link PartyWithProfile} items.
 	 */
-	public List<PartyWithProfile> getParticipants();
+	List<TeamOfPartiesAndProfiles> getTeams();
 
 	/**
-	 * Allows modification of SessionSettings to include a party. This is needed for
-	 * tournament auto-configuration of sessions.
+	 * Allows modification of SessionSettings to include a party. This is needed
+	 * for tournament auto-configuration of sessions.
 	 * 
-	 * @param partyprof the party with profile to be added
+	 * @param partyprofteam the {@link TeamOfPartiesAndProfiles} to be added
 	 * @return new modified SessionSettings object
 	 */
-	public SessionSettings with(PartyWithProfile partyprof);
+	SessionSettings with(TeamOfPartiesAndProfiles partyprofteam);
+
+	/**
+	 * 
+	 * @return all parties from all teams, as a flattened ordered list. The
+	 *         order : start with all parties from team 1, then all from team 2,
+	 *         etc. The specific order of the parties from a team depends on the
+	 *         protocol.
+	 */
+	List<PartyWithProfile> getAllParties();
 
 	/**
 	 * @param logger the logger where the protocol can log events to.
-	 * @return the an initialized and ready to use {@link SessionProtocol} that can
-	 *         handle this Negotiation.
+	 * @return the an initialized and ready to use {@link SessionProtocol} that
+	 *         can handle this Negotiation.
 	 */
 	@Override
-	public SessionProtocol getProtocol(Reporter logger);
+	SessionProtocol getProtocol(Reporter logger);
 
 }
Index: protocol/src/main/java/geniusweb/protocol/session/TeamOfPartiesAndProfiles.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/TeamOfPartiesAndProfiles.java	(revision 10)
+++ protocol/src/main/java/geniusweb/protocol/session/TeamOfPartiesAndProfiles.java	(revision 10)
@@ -0,0 +1,34 @@
+package geniusweb.protocol.session;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+
+import geniusweb.protocol.session.saop.SaopPartyWithProfile;
+import geniusweb.protocol.session.shaop.ShaopTeam;
+import geniusweb.references.PartyWithProfile;
+
+/**
+ * Contains a functional team, eg in SHAOP a team would be a SHAOP together with
+ * his COB partner. In SAOP this is just a SAOP party.
+ * 
+ * This is needed to specify the general SessionSettings functionality in a
+ * sufficient general way
+ *
+ */
+@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
+// we do not use JsonTypeInfo because we always use a specific Team within a protocol
+// and do not (de)serialize individual TeamOfPartiesAndProfiles.
+//@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
+@JsonSubTypes({ @JsonSubTypes.Type(value = SaopPartyWithProfile.class),
+		@JsonSubTypes.Type(value = ShaopTeam.class) })
+public interface TeamOfPartiesAndProfiles {
+	/**
+	 * 
+	 * @return the list of all parties involved, which is the parties in all
+	 *         teams combined.
+	 */
+	List<PartyWithProfile> getAllParties();
+}
Index: protocol/src/main/java/geniusweb/protocol/session/saop/SAOP.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/saop/SAOP.java	(revision 9)
+++ protocol/src/main/java/geniusweb/protocol/session/saop/SAOP.java	(revision 10)
@@ -65,8 +65,8 @@
 public class SAOP extends DefaultListenable<ProtocolEvent>
 		implements SessionProtocol {
-	private static final int TIME_MARGIN = 20;// ms extra delay after deadline
-	private static final int MINDURATION = 100;
-	private static final int MIN_SLEEP_TIME = 1000;
-	private static final int MAX_SLEEP_TIME = 60000;
+	public static final int TIME_MARGIN = 20;// ms extra delay after deadline
+	public static final int MINDURATION = 100;
+	public static final int MIN_SLEEP_TIME = 1000;
+	public static final int MAX_SLEEP_TIME = 60000;
 	private static final ProtocolRef SAOP = new ProtocolRef("SAOP");
 	private final Reporter log;
@@ -154,5 +154,5 @@
 			throws InterruptedException, IOException {
 		List<PartyWithProfile> participants = state.getSettings()
-				.getParticipants();
+				.getAllParties();
 		List<Reference> parties = participants.stream()
 				.map(parti -> (parti.getParty().getPartyRef()))
@@ -319,4 +319,6 @@
 			setState(state.with(new ProtocolException(message, party, e)));
 		}
+		log.log(Level.WARNING, "SAOP protocol intercepted error due to party "
+				+ party + ":" + message, e);
 	}
 
Index: protocol/src/main/java/geniusweb/protocol/session/saop/SAOPSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/saop/SAOPSettings.java	(revision 9)
+++ protocol/src/main/java/geniusweb/protocol/session/saop/SAOPSettings.java	(revision 10)
@@ -4,4 +4,5 @@
 import java.util.LinkedList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import com.fasterxml.jackson.annotation.JsonCreator;
@@ -11,9 +12,10 @@
 import geniusweb.protocol.session.SessionProtocol;
 import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.TeamOfPartiesAndProfiles;
 import geniusweb.references.PartyWithProfile;
 import tudelft.utilities.logging.Reporter;
 
 public class SAOPSettings implements SessionSettings {
-	private final List<PartyWithProfile> participants;
+	private final List<SaopPartyWithProfile> participants;
 	private final Deadline deadline;
 
@@ -28,5 +30,5 @@
 	@JsonCreator
 	public SAOPSettings(
-			@JsonProperty("participants") List<PartyWithProfile> participants,
+			@JsonProperty("participants") List<SaopPartyWithProfile> participants,
 			@JsonProperty("deadline") Deadline deadline) {
 		this.participants = participants;
@@ -51,5 +53,5 @@
 
 	@Override
-	public List<PartyWithProfile> getParticipants() {
+	public List<TeamOfPartiesAndProfiles> getTeams() {
 		return Collections.unmodifiableList(participants);
 	}
@@ -60,4 +62,11 @@
 	public Deadline getDeadline() {
 		return deadline;
+	}
+
+	@Override
+	public List<PartyWithProfile> getAllParties() {
+		return participants.stream()
+				.map(particip -> particip.getAllParties().get(0))
+				.collect(Collectors.toList());
 	}
 
@@ -101,7 +110,11 @@
 
 	@Override
-	public SessionSettings with(PartyWithProfile party) {
-		List<PartyWithProfile> newparts = new LinkedList<>(participants);
-		newparts.add(party);
+	public SessionSettings with(TeamOfPartiesAndProfiles party) {
+		if (!(party instanceof SaopPartyWithProfile))
+			throw new IllegalArgumentException(
+					"Added party must be SaopPartyWithProfile but got "
+							+ party);
+		List<SaopPartyWithProfile> newparts = new LinkedList<>(participants);
+		newparts.add((SaopPartyWithProfile) party);
 		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 9)
+++ protocol/src/main/java/geniusweb/protocol/session/saop/SAOPState.java	(revision 10)
@@ -16,6 +16,6 @@
 import geniusweb.progress.ProgressRounds;
 import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
 import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
-import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
 import geniusweb.protocol.session.DefaultSessionState;
 import geniusweb.protocol.session.SessionSettings;
@@ -61,24 +61,4 @@
 
 	/**
-	 * @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.
-	 * @param currentTimeMs the current time in ms since 1970, see
-	 *                      {@link System#currentTimeMillis()}
-	 * @return new SessionState with the action added as last action.
-	 */
-	public SAOPState with(PartyId actor, Action action) {
-		try {
-			return with1(actor, action);
-		} catch (ProtocolException e) {
-			List<Action> newactions = new LinkedList<>(getActions());
-			newactions.add(action);
-			return new SAOPState(newactions, getConnections(), getProgress(),
-					getSettings(), getPartyProfiles(), e);
-		}
-	}
-
-	/**
 	 * 
 	 * @param connection   the new {@link ProtocolToPartyConn}
@@ -164,4 +144,59 @@
 
 	/**
+	 * @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.
+	 * @param currentTimeMs the current time in ms since 1970, see
+	 *                      {@link System#currentTimeMillis()}
+	 * @return new SessionState with the action added as last action.
+	 */
+
+	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);
+		}
+
+		// 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 + ")");
+			}
+		} 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
+		} 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");
+		}
+
+		List<Action> newactions = new LinkedList<>(getActions());
+		newactions.add(action);
+		return new SAOPState(newactions, getConnections(), advanceProgress(),
+				getSettings(), getPartyProfiles(), null);
+	}
+
+	/**
 	 * Check up to nparticipants-1 steps back if there was an offer.
 	 * 
@@ -182,54 +217,4 @@
 	}
 
-	private SAOPState with1(PartyId actor, Action action)
-			throws ProtocolException {
-
-		if (actor == null) { // this is a bug
-			throw new IllegalArgumentException("actor must not be null");
-		}
-		if (!actor.equals(getNextActor())) {
-			throw new ProtocolException("Party does not have the turn ",
-					actor.getName());
-
-		}
-		if (action == null) {
-			throw new ProtocolException("action is null", actor.getName());
-		}
-		if (!actor.equals(action.getActor())) {
-			throw new ProtocolException(
-					"act contains wrong credentials: " + action,
-					actor.getName());
-		}
-
-		// check protocol is followed for specific actions
-		if (action instanceof Accept) {
-			Bid bid = getLastBid();
-			if (bid == null) {
-				throw new ProtocolException("Accept without a recent offer",
-						actor.getName());
-			}
-			if (!bid.equals(((Accept) action).getBid())) {
-				throw new ProtocolException(
-						"Party accepts a bid differing from the last offer ="
-								+ bid + ", action=" + action + ")",
-						actor.getName());
-			}
-		} 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
-		} else if (action instanceof EndNegotiation) {
-			// just act, we will get into the final state then.
-		} else {
-			throw new ProtocolException(
-					"Action " + action + " is not allowed in SAOP",
-					actor.getName());
-		}
-
-		List<Action> newactions = new LinkedList<>(getActions());
-		newactions.add(action);
-		return new SAOPState(newactions, getConnections(), advanceProgress(),
-				getSettings(), getPartyProfiles(), null);
-	}
-
 	/**
 	 * 
Index: protocol/src/main/java/geniusweb/protocol/session/saop/SaopPartyWithProfile.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/saop/SaopPartyWithProfile.java	(revision 10)
+++ protocol/src/main/java/geniusweb/protocol/session/saop/SaopPartyWithProfile.java	(revision 10)
@@ -0,0 +1,29 @@
+package geniusweb.protocol.session.saop;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import geniusweb.protocol.session.TeamOfPartiesAndProfiles;
+import geniusweb.references.PartyWithParameters;
+import geniusweb.references.PartyWithProfile;
+import geniusweb.references.ProfileRef;
+
+public class SaopPartyWithProfile extends PartyWithProfile
+		implements TeamOfPartiesAndProfiles {
+
+	@JsonCreator
+	public SaopPartyWithProfile(
+			@JsonProperty("party") PartyWithParameters party,
+			@JsonProperty("profile") ProfileRef profile) {
+		super(party, profile);
+	}
+
+	@Override
+	public List<PartyWithProfile> getAllParties() {
+		return Arrays.asList(this);
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/shaop/BareSHAOPState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/shaop/BareSHAOPState.java	(revision 10)
+++ protocol/src/main/java/geniusweb/protocol/session/shaop/BareSHAOPState.java	(revision 10)
@@ -0,0 +1,167 @@
+package geniusweb.protocol.session.shaop;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+
+import geniusweb.actions.Action;
+import geniusweb.actions.PartyId;
+import geniusweb.progress.Progress;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
+import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.SessionState;
+import geniusweb.references.PartyWithProfile;
+
+/**
+ * The state without all the "with" functioality to make this more readable.
+ * immutable. json-serializable.
+ */
+public abstract class BareSHAOPState implements SessionState {
+	protected final List<Action> actions;
+
+	protected final transient ProtocolToPartyConnections connections;
+
+	protected final Progress progress;
+
+	protected final SHAOPSettings settings;
+
+	protected final ProtocolException error;
+
+	protected final int actorNr;
+
+	protected final Map<PartyId, Integer> partyNumbers;
+
+	protected final Map<PartyId, Double> totalSpent;
+
+	/**
+	 * 
+	 * @param actions    value for actions done so far. null equals to empty
+	 *                   list
+	 * @param conns      the currently existing connections. Can be empty/null.
+	 *                   Each connection represents another party. Normally
+	 *                   there is exactly 1 connection for every party. The
+	 *                   protocol should check this.
+	 * @param progr      the {@link Progress} that governs this session. Can be
+	 *                   null if session did not yet start.
+	 * @param settings   the settings used for the session
+	 * @param e          the exception that occured, usually null. All errors
+	 *                   occuring due to faulty {@link Action}s translate to
+	 *                   {@link ProtocolException}s. All errors in our own code
+	 *                   are bugs (not ProtocolExceptions) and should result in
+	 *                   a throw.
+	 * @param actorNr    the actor that has the turn, index {@link #connections}
+	 * @param partyNrs   a map for each known PartyId to a number. The number is
+	 *                   the index in both {@link SHAOPSettings#getTeams()} and
+	 *                   in {@link #connections}. null is empyt map.
+	 * @param totalSpent total accumulated elicitation costs so far. only SHAOP
+	 *                   parties accumulate costs. null = empty map
+	 */
+	@JsonCreator
+	public BareSHAOPState(List<Action> actions,
+			ProtocolToPartyConnections conns, Progress progr,
+			SHAOPSettings settings, ProtocolException e, int actorNr,
+			Map<PartyId, Integer> partyNrs, Map<PartyId, Double> spent) {
+		if (conns == null) {
+			this.connections = new ProtocolToPartyConnections(
+					Collections.emptyList());
+		} else {
+			this.connections = conns;
+		}
+		if (actions == null) {
+			this.actions = new LinkedList<>();
+		} else {
+			this.actions = actions;
+		}
+		if (partyNrs == null) {
+			this.partyNumbers = Collections.emptyMap();
+		} else {
+			this.partyNumbers = partyNrs;
+		}
+		if (spent == null) {
+			this.totalSpent = Collections.emptyMap();
+		} else {
+			this.totalSpent = spent;
+		}
+
+		if (!connections.allunique()) {
+			throw new IllegalArgumentException(
+					"There can not be multiple connections for a party:"
+							+ conns);
+		}
+
+		this.progress = progr;
+		this.settings = settings;
+		this.error = e;
+		this.actorNr = actorNr;
+	}
+
+	public ProtocolToPartyConnections getConnections() {
+		return connections;
+	}
+
+	/**
+	 * 
+	 * @return party ID of current actorNr
+	 */
+	public PartyId getCurrentActor() {
+		return connections.get(actorNr).getParty();
+	}
+
+	/**
+	 * @param id the {@link PartyId}
+	 * @return the PartyWithProfile associated with that id.
+	 */
+	public PartyWithProfile getPartyProfile(PartyId id) {
+		return settings.getAllParties().get(partyNumbers.get(id));
+	}
+
+	/**
+	 * 
+	 * @return the index of each party
+	 */
+	public Map<PartyId, Integer> getPartyNumbers() {
+		return Collections.unmodifiableMap(partyNumbers);
+	}
+
+	/**
+	 * 
+	 * @return unmodifyable list of actions done so far.
+	 */
+	@Override
+	public List<Action> getActions() {
+		return Collections.unmodifiableList(actions);
+	}
+
+	@Override
+	public Progress getProgress() {
+		return progress;
+	}
+
+	@Override
+	public SessionSettings getSettings() {
+		return settings;
+	}
+
+	@Override
+	public boolean isFinal(long currentTimeMs) {
+		return error != null
+				|| (progress != null && progress.isPastDeadline(currentTimeMs));
+	}
+
+	@Override
+	public ProtocolException getError() {
+		return error;
+	}
+
+	@Override
+	public String toString() {
+		return this.getClass().getSimpleName() + "[" + actions + ","
+				+ connections + "," + progress + "," + settings + "," + error
+				+ "]";
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/shaop/SHAOP.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/shaop/SHAOP.java	(revision 10)
+++ protocol/src/main/java/geniusweb/protocol/session/shaop/SHAOP.java	(revision 10)
@@ -0,0 +1,403 @@
+package geniusweb.protocol.session.shaop;
+
+import java.io.IOException;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+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.Accept;
+import geniusweb.actions.Action;
+import geniusweb.actions.Comparison;
+import geniusweb.actions.ElicitComparison;
+import geniusweb.actions.EndNegotiation;
+import geniusweb.actions.Offer;
+import geniusweb.actions.PartyId;
+import geniusweb.deadline.Deadline;
+import geniusweb.events.ProtocolEvent;
+import geniusweb.party.inform.ActionDone;
+import geniusweb.party.inform.Finished;
+import geniusweb.party.inform.Inform;
+import geniusweb.party.inform.Settings;
+import geniusweb.party.inform.YourTurn;
+import geniusweb.progress.ProgressFactory;
+import geniusweb.protocol.CurrentNegoState;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConnFactory;
+import geniusweb.protocol.session.SessionProtocol;
+import geniusweb.protocol.session.SessionState;
+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;
+
+/**
+ * 
+ * This is similar to SAOP but there are two types of parties: a SHAOP party and
+ * an a COB party associated with each SHAOP party.
+ * <p>
+ * Each SHAOP must receive a parameter: elicitationcost containing a Double. A
+ * party of this type can execute a RequestComparison(userid) action, which
+ * increases the total bother with the elicitationcost. The call results in the
+ * associated COB party to execute a Comparison action.
+ * <p>
+ * A SHAOP party keeps the turn until it does an action that is not a
+ * {@link ElicitComparison}. A COB party also receives all ActionDone if a party
+ * does an action. The party takes an action only after CompareWithBid is
+ * received. It reacts with a Comparison action.
+ * <p>
+ * The Comparison/ComparisonTest actions are private between the
+ * {@link ShaopTeam}. A SHAOP party can do a {@link ElicitComparison} action
+ * at any time. These actions are completely transparent to the other parties
+ * and are not dependent or influencing the normal turn taking.
+ */
+public class SHAOP extends DefaultListenable<ProtocolEvent>
+		implements SessionProtocol {
+	/**
+	 * Tech note: We can not just extend SAOP because SHAOPState and
+	 * SHAOPSettings do not extend SAOPState and SAOPSettings
+	 */
+	public static final int TIME_MARGIN = 20;// ms extra delay after deadline
+	public static final int MINDURATION = 100;
+	public static final int MIN_SLEEP_TIME = 1000;
+	public static final int MAX_SLEEP_TIME = 60000;
+	private static final ProtocolRef SHAOP = new ProtocolRef("SHAOP");
+	private final Reporter log;
+
+	private SHAOPState state = null; // mutable!
+	private volatile AtomicBoolean isFinishedInfoSent = new AtomicBoolean(
+			false);
+	private volatile Timer deadlinetimer = null;
+	private List<Class> ALLOWED_ACTIONS = Arrays.asList(Offer.class,
+			ElicitComparison.class, Accept.class, EndNegotiation.class);
+
+	/**
+	 * 
+	 * @param state normally the initial state coming from SAOPSettings
+	 */
+	public SHAOP(SHAOPState state, Reporter logger) {
+		if (state == null) {
+			throw new NullPointerException("state must be not null");
+		}
+		if (state.getSettings().getDeadline().getDuration() < MINDURATION) {
+			throw new IllegalArgumentException(
+					"Duration must be at least " + MINDURATION);
+		}
+		this.log = logger;
+		this.state = state;
+	}
+
+	@Override
+	public synchronized void start(
+			ProtocolToPartyConnFactory connectionfactory) {
+
+		try {
+			connect(connectionfactory);
+			setDeadline();
+			setupParties();
+			nextTurn();
+		} catch (Throwable e) {
+			handleError("Failed to start up session", null, e);
+		}
+	}
+
+	@Override
+	public String getDescription() {
+		return "We iterate through all PartiesTuple's, first the shaop party and then the cob party. Each gets "
+				+ "YourTurn in clockwise order, after which they can do their next action. "
+				+ "No new participants after start. End after prescribed deadline or when some bid is unanimously Accepted."
+				+ "Parties can only act on their own behalf and only when it is their turn."
+				+ "COB requests and actions are stored in the actions list but not broadcasted."
+				+ "A SHAOP party keeps the turn until it does a non-COB request (Accept, Offer, EndNegotiation).";
+
+	}
+
+	@Override
+	public void addParticipant(PartyWithProfile party)
+			throws IllegalStateException {
+		throw new IllegalStateException(
+				"Dynamic joining a negotiation is not allowed in SHAOP");
+	}
+
+	@Override
+	public SessionState getState() {
+		return state;
+	}
+
+	@Override
+	public ProtocolRef getRef() {
+		return SHAOP;
+	}
+
+	/*******************************************************************
+	 * 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()
+				.getTeams().stream().map(team -> team.getAllParties())
+				.flatMap(List::stream).collect(Collectors.toList());
+		List<Reference> parties = participants.stream()
+				.map(parti -> (parti.getParty().getPartyRef()))
+				.collect(Collectors.toList());
+		List<ProtocolToPartyConn> connections = null;
+		log.log(Level.INFO, "SHAOP 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++) {
+			setState(this.state.with(connections.get(i)));
+		}
+	}
+
+	/**
+	 * 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,
+						"SHAOP 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, "SHAOP deadline set to "
+				+ new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
+						.format(new Date(System.currentTimeMillis()
+								+ deadline.getDuration() + TIME_MARGIN)));
+	}
+
+	/**
+	 * 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 : state.connections) {
+			conn.addListener(action -> actionRequest(conn, action));
+		}
+
+		for (ProtocolToPartyConn connection : state.connections) {
+			try {
+				sendSettings(connection);
+			} catch (IOException e) {
+				throw new ProtocolException("Failed to initialize",
+						connection.getParty().getName(), 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();
+		PartyWithProfile settings = state.getPartyProfile(partyid);
+		ProfileRef profile = settings.getProfile();
+		Parameters params = settings.getParty().getParameters();
+		connection.send(new Settings(connection.getParty(), profile, getRef(),
+				state.getProgress(), params));
+	}
+
+	/**
+	 * This is called when one of the party connections does an action.
+	 * Synchronized so that we always handle only 1 action at a time. This is
+	 * also called when a connection closes down, but then with a null action.
+	 * 
+	 * @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) {
+		try {
+			if (partyconn.getError() != null)
+				throw partyconn.getError();
+			actionRequest1(partyconn, action);
+		} catch (Throwable e) {
+			handleError("failed to handle action " + action,
+					partyconn.getParty().getName(), e);
+		}
+	}
+
+	protected synchronized void actionRequest1(
+			final ProtocolToPartyConn partyconn, final Action action)
+			throws ProtocolException, IOException {
+		PartyId partyid = partyconn.getParty();
+		if (state.isFinal(System.currentTimeMillis())) {
+			return;
+		}
+
+		if (action == null)
+			throw new ProtocolException("Party sent a null action",
+					partyid.getName());
+
+		// check if action allowed
+		if (state.isShaopParty(partyid)) {
+			if (!ALLOWED_ACTIONS.contains(action.getClass())) {
+				throw new ProtocolException(
+						"Illegal action for SHAOP Party:" + action,
+						partyid.getName());
+			}
+		} else {
+			if (!(action instanceof Comparison))
+				throw new ProtocolException(
+						"Illegal action for COB Party:" + action,
+						partyid.getName());
+		}
+
+		// broadcast, except if this is about eliciting comparison.
+		if (action instanceof ElicitComparison
+				|| action instanceof Comparison) {
+			// send actionDone only to the partner
+			setState(state.with(partyconn.getParty(), action));
+			state.connections.get(state.getPartner(partyid))
+					.send(new ActionDone(action));
+		} else {
+			if (!partyconn.getParty().equals(state.getCurrentActor()))
+				throw new ProtocolException(
+						"Party acts without having the turn",
+						partyid.getName());
+			setState(state.with(partyconn.getParty(), action));
+			state.connections.broadcast(new ActionDone(action));
+			if (!state.isFinal(System.currentTimeMillis()))
+				nextTurn();
+		}
+
+	}
+
+	/**
+	 * Signal the current participant it's his turn
+	 * 
+	 * @throws IOException
+	 */
+	private synchronized void nextTurn() {
+		PartyId party = state.getCurrentActor();
+		try {
+			state.getConnections().get(party).send(new YourTurn());
+		} catch (IOException e) {
+			handleError("failed to send YourTurn", party.getName(), e);
+		}
+	}
+
+	/**
+	 * 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 String party, final Throwable e) {
+		log.log(Level.WARNING, "SHAOP protocol intercepted error due to party "
+				+ party + ": " + message, e);
+		if (e instanceof ProtocolException) {
+			setState(state.with((ProtocolException) e));
+		} else {
+			setState(state.with(new ProtocolException(message, party, 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(SHAOPState 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.getAgreement());
+		state.connections.stream().forEach(conn -> 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/shaop/SHAOPSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/shaop/SHAOPSettings.java	(revision 10)
+++ protocol/src/main/java/geniusweb/protocol/session/shaop/SHAOPSettings.java	(revision 10)
@@ -0,0 +1,138 @@
+package geniusweb.protocol.session.shaop;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import geniusweb.deadline.Deadline;
+import geniusweb.protocol.session.SessionProtocol;
+import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.TeamOfPartiesAndProfiles;
+import geniusweb.references.PartyWithProfile;
+import tudelft.utilities.logging.Reporter;
+
+public class SHAOPSettings implements SessionSettings {
+	private final List<ShaopTeam> participants;
+	private final Deadline deadline;
+	private final transient List<PartyWithProfile> allParties = new LinkedList<>();
+
+	/**
+	 * @param teams    the list of {@link ShaopTeam}s containing the shaop
+	 *                 parties and their partner cob There must be at least 2 to
+	 *                 run the SHAOP protocol. But SHAOP can be initialized with
+	 *                 less, for use in TournamentSettings.
+	 * @param deadline the deadline settings for the session
+	 */
+	@JsonCreator
+	public SHAOPSettings(@JsonProperty("participants") List<ShaopTeam> teams,
+			@JsonProperty("deadline") Deadline deadline) {
+		if (teams == null)
+			throw new IllegalArgumentException("participants must not be null");
+		if (deadline == null)
+			throw new IllegalArgumentException("deadline must not be null");
+		this.participants = teams;
+		this.deadline = deadline;
+		// notice, order here is crucial because of way #isShaopParty works.
+		for (ShaopTeam tuple : teams) {
+			allParties.add(tuple.getShaop());
+			allParties.add(tuple.getCob());
+		}
+	}
+
+	@Override
+	public Double getMaxRunTime() {
+		return deadline.getDuration() / 1000d;
+	}
+
+	@Override
+	public SessionProtocol getProtocol(Reporter logger) {
+		return new SHAOP(new SHAOPState(this), logger);
+	}
+
+	@Override
+	public List<TeamOfPartiesAndProfiles> getTeams() {
+		// cast needed...
+		return new ArrayList<TeamOfPartiesAndProfiles>(participants);
+	}
+
+	/**
+	 * 
+	 * @param n an index number into the {@link #getTeams()} list.
+	 * @return true iff that participant is a SHAOP party.
+	 */
+	public boolean isShaopParty(int n) {
+		return (n & 1) == 0;
+	}
+
+	/**
+	 * @return the deadline for this negotiation
+	 */
+	public Deadline getDeadline() {
+		return deadline;
+	}
+
+	/**
+	 * 
+	 * @return list of all parties, the even parties (0=first element in list)
+	 *         being the SHAOP party and the next in the list the partner COB
+	 *         party.
+	 */
+	public List<PartyWithProfile> getAllParties() {
+		return participants.stream().map(team -> team.getAllParties())
+				.flatMap(Collection::stream).collect(Collectors.toList());
+	}
+
+	@Override
+	public SessionSettings with(TeamOfPartiesAndProfiles party) {
+		if (!(party instanceof ShaopTeam))
+			throw new IllegalArgumentException(
+					"Added party must be ShaopTeam but got " + party);
+		List<ShaopTeam> newparts = new LinkedList<>(participants);
+		newparts.add((ShaopTeam) party);
+		return new SHAOPSettings(newparts, 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;
+		SHAOPSettings other = (SHAOPSettings) 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 String toString() {
+		return "SHAOPSettings" + participants + "," + deadline + "]";
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/shaop/SHAOPState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/shaop/SHAOPState.java	(revision 10)
+++ protocol/src/main/java/geniusweb/protocol/session/shaop/SHAOPState.java	(revision 10)
@@ -0,0 +1,329 @@
+package geniusweb.protocol.session.shaop;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import geniusweb.actions.Accept;
+import geniusweb.actions.Action;
+import geniusweb.actions.Comparison;
+import geniusweb.actions.ElicitComparison;
+import geniusweb.actions.EndNegotiation;
+import geniusweb.actions.Offer;
+import geniusweb.actions.PartyId;
+import geniusweb.issuevalue.Bid;
+import geniusweb.progress.Progress;
+import geniusweb.progress.ProgressRounds;
+import geniusweb.protocol.ProtocolException;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConn;
+import geniusweb.protocol.partyconnection.ProtocolToPartyConnections;
+import geniusweb.references.PartyWithProfile;
+
+/**
+ * Immutable.
+ */
+public class SHAOPState extends BareSHAOPState {
+
+	private static final double DEFAULT_ELICITATATION_COST = 0.1d;
+
+	/**
+	 * see {@link BareSHAOPState}.
+	 */
+	@JsonCreator
+	public SHAOPState(@JsonProperty("actions") List<Action> actions,
+			@JsonProperty("connections") ProtocolToPartyConnections conns,
+			@JsonProperty("progress") Progress progress,
+			@JsonProperty("settings") SHAOPSettings settings,
+			@JsonProperty("error") ProtocolException e,
+			@JsonProperty("actorNr") int actorNr,
+			@JsonProperty("partyNumbers") Map<PartyId, Integer> partytNumbers,
+			@JsonProperty("totalSpent") Map<PartyId, Double> spent) {
+		super(actions, conns, progress, settings, e, actorNr, partytNumbers,
+				spent);
+	}
+
+	/**
+	 * Creates the initial state from the given settings
+	 * 
+	 * @param settings the {@link SHAOPSettings}
+	 */
+	public SHAOPState(SHAOPSettings settings) {
+		this(Collections.emptyList(), null, null, settings, null, 0, null,
+				null);
+	}
+
+	/**
+	 * 
+	 * @param id the {@link PartyId}
+	 * @return the {@link PartyWithProfile} for that party
+	 */
+	public PartyWithProfile getSettings(PartyId id) {
+		return settings.getAllParties().get(partyNumbers.get(id));
+	}
+
+	/**
+	 * 
+	 * @param party
+	 * @return true iff party is a SHAOP party.
+	 */
+	public boolean isShaopParty(PartyId party) {
+		return settings.isShaopParty(partyNumbers.get(party));
+	}
+
+	/**
+	 * 
+	 * @param party a Party Id
+	 * @return the PartyId of the party
+	 */
+	public PartyId getPartner(PartyId party) {
+		return connections.get(partyNumbers.get(party) ^ 1).getParty();
+	}
+
+	@Override
+	public boolean isFinal(long currentTimeMs) {
+		List<Action> acts = getActions();
+		return super.isFinal(currentTimeMs) || getAgreement() != null
+				|| (!acts.isEmpty()
+						&& acts.get(acts.size() - 1) instanceof EndNegotiation);
+	}
+
+	@Override
+	public SHAOPSettings getSettings() {
+		return settings;
+	}
+
+	/**
+	 * search back through the actions for an offer. We can not smarly limit the
+	 * search size because there can be an unlimited number of cob Comparison
+	 * actions anywhere in the list.
+	 * 
+	 * @return Bid from the most recent offer, or null if no such offer
+	 */
+	private Bid getLastBid() {
+		for (int n = actions.size() - 1; n >= 0; n--) {
+			Action action = actions.get(n);
+			if (action instanceof Offer) {
+				return ((Offer) action).getBid();
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * 
+	 * @param connection the new {@link ProtocolToPartyConn} to a party
+	 * @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.
+	 *         IMPORTANT. The connections MUST be made in the same order as
+	 *         {@link SHAOPSettings#getTeams()}.
+	 */
+	public SHAOPState with(ProtocolToPartyConn connection) {
+		// Only called from the SHAOP initialization phase.
+		ProtocolToPartyConnections newconns = connections.with(connection);
+		Map<PartyId, Integer> newNumbers = new HashMap<>(partyNumbers);
+		newNumbers.put(connection.getParty(), connections.size());
+		return new SHAOPState(actions, newconns, progress, settings, null,
+				actorNr, newNumbers, totalSpent);
+	}
+
+	/**
+	 * 
+	 * @param e             the error that occured
+	 * @param currentTimeMs the current time in ms since 1970, see
+	 *                      {@link System#currentTimeMillis()}
+	 * @return a new state with the error set.
+	 */
+	public SHAOPState with(ProtocolException e) {
+		return new SHAOPState(actions, connections, progress, settings, e,
+				actorNr, partyNumbers, totalSpent);
+	}
+
+	/**
+	 * Sets the progress for this session. call is ignored if progress already
+	 * set.
+	 * 
+	 * @param newprogress the new progress
+	 * @return new SAOPState with the progress set
+	 */
+	public SHAOPState with(Progress newprogress) {
+		if (newprogress == null) {
+			throw new IllegalArgumentException("newprogress must be not null");
+		}
+		if (progress != null) {
+			return this;
+		}
+		return new SHAOPState(actions, connections, newprogress, settings,
+				error, actorNr, partyNumbers, totalSpent);
+	}
+
+	@Override
+	public Bid getAgreement() {
+		List<Action> acts = getActions();
+		if (acts.isEmpty())
+			return null;
+
+		int requiredaccepts = getSettings().getTeams().size() - 1;
+
+		// check that there are requiredaccepts and find the offer before that
+		for (int n = acts.size() - 1; n >= 0; n--) {
+			Action act = acts.get(n);
+			if (act instanceof Comparison || act instanceof ElicitComparison)
+				continue;
+			if (requiredaccepts == 0 && act instanceof Offer)
+				return ((Offer) act).getBid();
+			if (!(act instanceof Accept))
+				return null;
+			requiredaccepts--;
+		}
+		return null;
+	}
+
+	/**
+	 * @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.
+	 * @param currentTimeMs the current time in ms since 1970, see
+	 *                      {@link System#currentTimeMillis()}
+	 * @return new SessionState with the action added as last action.
+	 */
+
+	public SHAOPState with(PartyId actor, Action action)
+			throws IllegalArgumentException {
+
+		if (actor == null) { // this is a bug
+			throw new IllegalArgumentException("actor must not be null");
+		}
+		if (action == null) {
+			throw new IllegalArgumentException("action is null");
+		}
+		if (!actor.equals(action.getActor())) {
+			throw new IllegalArgumentException(
+					"act contains wrong credentials: " + action);
+		}
+
+		if (action instanceof ElicitComparison) {
+			return withShaopInternalAction(actor, (ElicitComparison) action);
+		}
+		if (action instanceof Comparison) {
+			return withCobInternalAction(actor, (Comparison) action);
+		}
+
+		return withShaopRealAction(actor, action);
+
+	}
+
+	private SHAOPState withCobInternalAction(PartyId actor, Comparison action)
+			throws IllegalArgumentException {
+		if (isShaopParty(actor))
+			throw new IllegalArgumentException(
+					"Illegal action, actor is not a cob party:" + action);
+		return withAction(action);
+	}
+
+	/**
+	 * 
+	 * @param actor  actor, supposedly SHAOP, did Elicit action
+	 * @param action an elicit action
+	 * @return new state
+	 * @throws ProtocolException
+	 */
+	private SHAOPState withShaopInternalAction(PartyId actor,
+			ElicitComparison action) throws IllegalArgumentException {
+		if (!isShaopParty(actor))
+			throw new IllegalArgumentException(
+					"Illegal action, actor is not a shaop party:" + action);
+		return withAction(action);
+	}
+
+	/**
+	 * update action, actorNr, and progress
+	 * 
+	 * @param action the action that was done. Elicit actions do not update
+	 *               progress
+	 * @return
+	 */
+	private SHAOPState withAction(Action action) {
+		Progress newprogress = progress;
+		Map<PartyId, Double> newSpent = new HashMap<>(totalSpent);
+		PartyId partyid = action.getActor();
+
+		List<Action> newactions = new LinkedList<>(getActions());
+		newactions.add(action);
+		int newActor = actorNr;
+		if (action instanceof ElicitComparison) {
+			Object cost = getPartyProfile(partyid).getParty().getParameters()
+					.get("elicitationcost");
+			if (!(cost instanceof Double)) {
+				cost = DEFAULT_ELICITATATION_COST;
+			}
+			Double oldSpent = totalSpent.get(partyid);
+			if (oldSpent == null) {
+				oldSpent = 0d;
+			}
+			newSpent.put(partyid, oldSpent + (Double) cost);
+
+		} else if (action instanceof Comparison) {
+			// nothing, turn remains with current SHAOP party
+		} else {
+			// shaop party does real action, move to next SHAOP party.
+			newActor = (actorNr + 2) % settings.getTeams().size();
+			if (newprogress instanceof ProgressRounds) {
+				newprogress = ((ProgressRounds) newprogress).advance();
+			}
+		}
+		return new SHAOPState(newactions, connections, newprogress, settings,
+				null, newActor, partyNumbers, newSpent);
+
+	}
+
+	/**
+	 * party that did a real action
+	 * 
+	 * @param actor
+	 * @param action
+	 * @return new state.
+	 * @throws ProtocolException
+	 */
+	private SHAOPState withShaopRealAction(PartyId actor, Action action)
+			throws IllegalArgumentException {
+		if (!isShaopParty(actor))
+			throw new IllegalArgumentException(
+					"Illegal action for COB party" + action);
+		// real action only allowed if party has the turn.
+		if (!actor.equals(connections.get(actorNr).getParty())) {
+			throw new IllegalArgumentException("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 + ")");
+			}
+		} 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
+		} 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 SHAOP");
+		}
+		return withAction(action);
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/session/shaop/ShaopTeam.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/session/shaop/ShaopTeam.java	(revision 10)
+++ protocol/src/main/java/geniusweb/protocol/session/shaop/ShaopTeam.java	(revision 10)
@@ -0,0 +1,93 @@
+package geniusweb.protocol.session.shaop;
+
+import java.util.Arrays;
+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.JsonProperty;
+
+import geniusweb.actions.Comparison;
+import geniusweb.protocol.session.TeamOfPartiesAndProfiles;
+import geniusweb.references.PartyWithProfile;
+
+/**
+ * A tuple of a SHOAP party and a COB party. Part of the settings.
+ *
+ */
+@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
+public class ShaopTeam implements TeamOfPartiesAndProfiles {
+	private final PartyWithProfile shaop;
+	private final PartyWithProfile cob;
+
+	/**
+	 * @param shaop {@link PartyWithProfile} participants supporting SHAOP
+	 *              behaviour. SHAOP behaviour has parameters:
+	 *              <ul>
+	 *              <li>elicitationcost: the value must be a double number
+	 *              indicating the costs of each RequestComparison action.
+	 *              <li>credit: the credit the party has to start with. The sum
+	 *              of the elicitationcost should stay below this.
+	 *              </ul>
+	 * @param cob   {@link PartyWithProfile} party, must support the COB
+	 *              behaviour. This is the cob partie that the shaop requests
+	 *              for {@link Comparison} will be forwarded to
+	 */
+	@JsonCreator
+	public ShaopTeam(@JsonProperty("shoap") PartyWithProfile shaop,
+			@JsonProperty("cob") PartyWithProfile cob) {
+		this.shaop = shaop;
+		this.cob = cob;
+	}
+
+	@Override
+	public List<PartyWithProfile> getAllParties() {
+		return Arrays.asList(shaop, cob);
+	}
+
+	@Override
+	public String toString() {
+		return "Parties[" + shaop + "," + cob + "]";
+	}
+
+	public PartyWithProfile getShaop() {
+		return shaop;
+	}
+
+	public PartyWithProfile getCob() {
+		return cob;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((cob == null) ? 0 : cob.hashCode());
+		result = prime * result + ((shaop == null) ? 0 : shaop.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;
+		ShaopTeam other = (ShaopTeam) obj;
+		if (cob == null) {
+			if (other.cob != null)
+				return false;
+		} else if (!cob.equals(other.cob))
+			return false;
+		if (shaop == null) {
+			if (other.shaop != null)
+				return false;
+		} else if (!shaop.equals(other.shaop))
+			return false;
+		return true;
+	}
+
+}
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsProtocol.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsProtocol.java	(revision 9)
+++ protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsProtocol.java	(revision 10)
@@ -81,6 +81,8 @@
 			long now = System.currentTimeMillis();
 			if (sessionstate.isFinal(now)) {
+				// List<TeamOfPartiesAndProfiles> participants =
+				// sessionstate.getSettings().getParticipants()
 				SessionResult result = new SessionResult(
-						sessionstate.getSettings().getParticipants(),
+						sessionstate.getSettings().getAllParties(),
 						sessionstate.getAgreement(), sessionstate.getError());
 				state = state.with(result);
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsSettings.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsSettings.java	(revision 9)
+++ protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsSettings.java	(revision 10)
@@ -1,4 +1,6 @@
 package geniusweb.protocol.tournament.allpermutations;
 
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.Collections;
 import java.util.List;
@@ -7,7 +9,14 @@
 import com.fasterxml.jackson.annotation.JsonProperty;
 
+import geniusweb.profile.Profile;
 import geniusweb.protocol.session.SessionSettings;
+import geniusweb.protocol.session.TeamOfPartiesAndProfiles;
+import geniusweb.protocol.session.saop.SAOPSettings;
+import geniusweb.protocol.session.saop.SaopPartyWithProfile;
+import geniusweb.protocol.session.shaop.SHAOPSettings;
+import geniusweb.protocol.session.shaop.ShaopTeam;
 import geniusweb.protocol.tournament.TournamentProtocol;
 import geniusweb.protocol.tournament.TournamentSettings;
+import geniusweb.references.Parameters;
 import geniusweb.references.PartyRef;
 import geniusweb.references.PartyWithParameters;
@@ -30,10 +39,32 @@
  * Takes a number of parties and profiles and makes all permutations of them
  * with the given number of parties and profiles. All profiles must be in the
- * same domain. All sessions are based on the given sessionsettings (can be any
- * session protocol). The parties for each tournament session are added to this
- * basic sessionsettings.
- *
+ * same domain.
+ * <p>
+ * All sessions are based on the given {@link SessionSettings} (can be
+ * {@link SAOPSettings} or {@link SHAOPSettings}). The parties for each
+ * tournament session are added to this basic sessionsettings. sessionsettings
+ * contains basically all the normal run-sessionsettings. This is used as a
+ * "template" for all sessions of the tournament. You can put any use any
+ * session setting here, and each session will be run according to the protocol
+ * you select here.
+ * 
+ * The participants list usually is empty. The AllPermutationsProtocol adds the
+ * the required parties to this list. So if you provide a non-empty list here,
+ * then these parties would be present in every session in the tournament.
+ * <p>
+ * If the tournament is used with a SHAOP sessionsettings, a number of rules are
+ * used to generate the COB party from the settings.
+ * 
+ * The COB party is assumed to be the comparebids-1.0.0 party on the same server
+ * as the party you provide. So if you provide http://server/../party1-1.2.3
+ * then the cob party will be assumed to be at
+ * http://server/../comparebids-1.0.0. The provided parties that you provide are
+ * the SHAOP parties (you can also use SAOP parties). The provided profile is
+ * also given to the SHAOP party. Be aware that you typically need a parameter
+ * like partial=10. The COB party gets the profile as specified, but without the
+ * query part. The COB and SHAOP party are thus a fixed pair.
  */
 public class AllPermutationsSettings implements TournamentSettings {
+
 	private final List<PartyWithParameters> parties;
 	private final List<ProfileRef> profiles;
@@ -49,5 +80,6 @@
 	 *                          we use PermutationsWithoutReturn to create the
 	 *                          parties.
-	 * @param profiles
+	 * @param profiles          list of available {@link Profile}s to be
+	 *                          permutated over the parties
 	 * @param partiesPerSession number of parties per session, must be at least
 	 *                          2.
@@ -97,5 +129,5 @@
 	 */
 	public ImmutableList<SessionSettings> permutations() {
-		ImmutableList<ImmutableList<PartyWithProfile>> partylistlist;
+		ImmutableList<ImmutableList<TeamOfPartiesAndProfiles>> partylistlist;
 		ImmutableList<PartyWithParameters> partieslist = new FixedList<PartyWithParameters>(
 				parties);
@@ -166,7 +198,7 @@
 	 */
 	private SessionSettings createSetting(
-			ImmutableList<PartyWithProfile> partyproflist) {
+			ImmutableList<TeamOfPartiesAndProfiles> partyproflist) {
 		SessionSettings settings = sessionsettings;
-		for (PartyWithProfile partyprof : partyproflist) {
+		for (TeamOfPartiesAndProfiles partyprof : partyproflist) {
 			settings = settings.with(partyprof);
 		}
@@ -181,9 +213,9 @@
 	 * @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 PartyWithProfile}s. The sessionsets
-	 *         are made by making all combinations of parties and profiles.
-	 *         Profiles are drawn with replace.
-	 */
-	private ImmutableList<ImmutableList<PartyWithProfile>> getParticipants(
+	 *         session: a list with {@link TeamOfPartiesAndProfiles}s. The
+	 *         sessionsets are made by making all combinations of parties and
+	 *         profiles. Profiles are drawn with replace.
+	 */
+	private ImmutableList<ImmutableList<TeamOfPartiesAndProfiles>> getParticipants(
 			ImmutableList<PartyWithParameters> parties,
 			ImmutableList<ProfileRef> profiles, int n,
@@ -205,6 +237,6 @@
 				partiesPermutations, profilesPermutations);
 
-		return new MapList<Tuple<ImmutableList<PartyWithParameters>, ImmutableList<ProfileRef>>, ImmutableList<PartyWithProfile>>(
-				tuple -> partyprofilelist(tuple), tuples);
+		return new MapList<Tuple<ImmutableList<PartyWithParameters>, ImmutableList<ProfileRef>>, ImmutableList<TeamOfPartiesAndProfiles>>(
+				tuple -> teamlist(tuple), tuples);
 	}
 
@@ -213,20 +245,60 @@
 	 * @param tuple a tuple with (1) ImmutableList<PartyRef> (2)
 	 *              ImmutableList<ProfileRef>
-	 * @return list of PartyWithProfile, each picked from the two lists in
-	 *         order.
-	 */
-	private ImmutableList<PartyWithProfile> partyprofilelist(
+	 * @return list of TeamOfPartiesAndProfiles, each picked from the two lists
+	 *         in order.
+	 */
+	private ImmutableList<TeamOfPartiesAndProfiles> teamlist(
 			Tuple<ImmutableList<PartyWithParameters>, ImmutableList<ProfileRef>> tuple) {
-		Function2<PartyWithParameters, ProfileRef, PartyWithProfile> //
-		makeparty = new Function2<PartyWithParameters, ProfileRef, PartyWithProfile>() {
+		Function2<PartyWithParameters, ProfileRef, TeamOfPartiesAndProfiles> //
+		makeparty = new Function2<PartyWithParameters, ProfileRef, TeamOfPartiesAndProfiles>() {
 			@Override
-			public PartyWithProfile apply(PartyWithParameters party,
+			public TeamOfPartiesAndProfiles apply(PartyWithParameters party,
 					ProfileRef profile) {
-				return new PartyWithProfile(party, profile);
+				if (sessionsettings instanceof SAOPSettings)
+					return new SaopPartyWithProfile(party, profile);
+				else if (sessionsettings instanceof SHAOPSettings) {
+					return new ShaopTeam(new PartyWithProfile(party, profile),
+							makeCob(party.getPartyRef(), profile));
+				} else
+					throw new IllegalArgumentException(
+							"Unknown/unsupported protocol "
+									+ sessionsettings.getClass());
 			}
+
 		};
-		return new MapThreadList<PartyWithProfile, PartyWithParameters, ProfileRef>(
+		return new MapThreadList<TeamOfPartiesAndProfiles, PartyWithParameters, ProfileRef>(
 				makeparty, tuple.get1(), tuple.get2());
 	}
 
+	/**
+	 * Extract the raw URL, without the part after the question mark
+	 * 
+	 * @param party   a party reference (for another party)
+	 * @param profile a profile that may have a filter like "?partial=XX"
+	 * @return profile without the filter and with a cob party on the same
+	 *         machine. IT IS ASSUMED that there will the default
+	 *         "comparebids-1.0.0" party on the same machine as where the other
+	 *         party is
+	 */
+	protected static PartyWithProfile makeCob(PartyRef party,
+			ProfileRef profile) {
+		try {
+			URI profileuri = profile.getURI();
+			URI partyuri = party.getURI();
+			ProfileRef cobprof = new ProfileRef(profileuri.getScheme() + "://"
+					+ profileuri.getAuthority() + profileuri.getPath());
+			String partypath = partyuri.getPath();
+			partypath = partypath.substring(0, partypath.lastIndexOf('/'));
+			PartyWithParameters cobparty = new PartyWithParameters(new PartyRef(
+					partyuri.getScheme() + "://" + partyuri.getAuthority()
+							+ partypath + "/comparebids-1.0.0"),
+					new Parameters());
+			return new PartyWithProfile(cobparty, cobprof);
+
+		} catch (URISyntaxException e) {
+			throw new IllegalArgumentException(
+					"Failed making cob party with profile " + profile, e);
+		}
+	}
+
 }
Index: protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsState.java
===================================================================
--- protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsState.java	(revision 9)
+++ protocol/src/main/java/geniusweb/protocol/tournament/allpermutations/AllPermutationsState.java	(revision 10)
@@ -55,5 +55,5 @@
 	 */
 	public AllPermutationsState with(SessionResult result) {
-		if (!getNextSettings().getParticipants()
+		if (!getNextSettings().getAllParties()
 				.equals(result.getParticipants())) {
 			throw new IllegalArgumentException("Inconsistent session result");
