diff --git a/src/main/java/org/olat/course/assessment/AssessmentModeCoordinationService.java b/src/main/java/org/olat/course/assessment/AssessmentModeCoordinationService.java
index 038994c6fe44f469526bf1987750cab894446ee2..dadd9e7771ac8ec1c6d1ee8a91307b6ed813ee34 100644
--- a/src/main/java/org/olat/course/assessment/AssessmentModeCoordinationService.java
+++ b/src/main/java/org/olat/course/assessment/AssessmentModeCoordinationService.java
@@ -22,6 +22,7 @@ package org.olat.course.assessment;
 import java.util.Date;
 
 import org.olat.basesecurity.IdentityRef;
+import org.olat.core.id.Identity;
 import org.olat.course.assessment.AssessmentMode.Status;
 import org.olat.course.assessment.model.AssessmentModeStatistics;
 import org.olat.course.assessment.model.TransientAssessmentMode;
@@ -59,7 +60,7 @@ public interface AssessmentModeCoordinationService {
 	
 	public AssessmentMode startAssessment(AssessmentMode assessmentMode);
 	
-	public AssessmentMode stopAssessment(AssessmentMode assessmentMode, boolean pullTestSessions, boolean withDisadvantaged);
+	public AssessmentMode stopAssessment(AssessmentMode assessmentMode, boolean pullTestSessions, boolean withDisadvantaged, Identity doer);
 	
 	public AssessmentModeStatistics getStatistics(AssessmentMode assessmentMode);
 	
diff --git a/src/main/java/org/olat/course/assessment/manager/AssessmentModeCoordinationServiceImpl.java b/src/main/java/org/olat/course/assessment/manager/AssessmentModeCoordinationServiceImpl.java
index 4588cee3928183f70177709570101fb1aea3c728..a910e6f4dff91b8418b81c3a1af95ce8e82fb5d0 100644
--- a/src/main/java/org/olat/course/assessment/manager/AssessmentModeCoordinationServiceImpl.java
+++ b/src/main/java/org/olat/course/assessment/manager/AssessmentModeCoordinationServiceImpl.java
@@ -32,7 +32,9 @@ import org.apache.logging.log4j.Logger;
 import org.olat.basesecurity.IdentityRef;
 import org.olat.basesecurity.model.IdentityRefImpl;
 import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.services.taskexecutor.TaskExecutorManager;
 import org.olat.core.gui.control.Event;
+import org.olat.core.id.Identity;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.cache.CacheWrapper;
 import org.olat.core.util.coordinate.CoordinatorManager;
@@ -51,6 +53,8 @@ import org.olat.course.assessment.model.AssessmentModeStatistics;
 import org.olat.course.assessment.model.CoordinatedAssessmentMode;
 import org.olat.course.assessment.model.TransientAssessmentMode;
 import org.olat.group.ui.edit.BusinessGroupModifiedEvent;
+import org.olat.ims.qti21.AssessmentTestSession;
+import org.olat.ims.qti21.manager.AssessmentTestSessionDAO;
 import org.olat.modules.dcompensation.manager.DisadvantageCompensationDAO;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntryStatusEnum;
@@ -74,6 +78,8 @@ public class AssessmentModeCoordinationServiceImpl implements AssessmentModeCoor
 	@Autowired
 	private DB dbInstance;
 	@Autowired
+	private TaskExecutorManager taskExecutor;
+	@Autowired
 	private AssessmentModule assessmentModule;
 	@Autowired
 	private RepositoryEntryDAO repositoryEntryDao;
@@ -84,6 +90,8 @@ public class AssessmentModeCoordinationServiceImpl implements AssessmentModeCoor
 	@Autowired
 	private AssessmentModeManagerImpl assessmentModeManager;
 	@Autowired
+	private AssessmentTestSessionDAO assessmentTestSessionDao;
+	@Autowired
 	private DisadvantageCompensationDAO disadvantageCompensationDao;
 	
 	private Map<Long,CoordinatedAssessmentMode> coordinatedModes = new ConcurrentHashMap<>();
@@ -460,7 +468,7 @@ public class AssessmentModeCoordinationServiceImpl implements AssessmentModeCoor
 	}
 
 	@Override
-	public AssessmentMode stopAssessment(AssessmentMode mode, boolean pullTestSessions, boolean withDisadvantaged) {
+	public AssessmentMode stopAssessment(AssessmentMode mode, boolean pullTestSessions, boolean withDisadvantaged, Identity doer) {
 		mode = assessmentModeManager.getAssessmentModeById(mode.getKey());
 		
 		EndStatus endStatus;
@@ -482,16 +490,33 @@ public class AssessmentModeCoordinationServiceImpl implements AssessmentModeCoor
 				}
 			}
 			
-			if(assessedIdentityKeys.contains(252744713l)) {
-				System.out.println();
-			}
-			
 			endStatus = partial ? EndStatus.withoutDisadvantage : EndStatus.all;
 		}
 		
+		if(pullTestSessions) {
+			pullSessions(mode, assessedIdentityKeys, doer);
+		}
+		
 		return stopAssessment(mode, assessedIdentityKeys, endStatus);
 	}
 	
+	private void pullSessions(AssessmentMode mode, Set<Long> assessedIdentityKeys, Identity doer) {
+		List<IdentityRef> identityRefs = assessedIdentityKeys.stream()
+				.map(IdentityRefImpl::new)
+				.collect(Collectors.toList());
+		List<AssessmentTestSession> testSessions = assessmentTestSessionDao
+				.getRunningTestSessions(mode.getRepositoryEntry(), mode.getElementAsList(), identityRefs);
+		if(!testSessions.isEmpty()) {
+			List<Long> testSessionKeys = testSessions.stream()
+					.map(AssessmentTestSession::getKey)
+					.collect(Collectors.toList());
+			
+			Long doerKey = doer == null ? null : doer.getKey();
+			PullTestSessionsTask task = new PullTestSessionsTask(mode.getRepositoryEntry().getKey(), testSessionKeys, doerKey);
+			taskExecutor.schedule(task, 30000);
+		}
+	}
+	
 	private AssessmentMode stopAssessment(AssessmentMode mode, Set<Long> assessedIdentityKeys, EndStatus endStatus) {
 		String cmd;
 		if(mode.getFollowupTime() > 0) {
diff --git a/src/main/java/org/olat/course/assessment/manager/PullTestSessionsTask.java b/src/main/java/org/olat/course/assessment/manager/PullTestSessionsTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..7bfaaeab1fe41468ab1d043292905da552886e0e
--- /dev/null
+++ b/src/main/java/org/olat/course/assessment/manager/PullTestSessionsTask.java
@@ -0,0 +1,118 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.assessment.manager;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimerTask;
+
+import org.apache.logging.log4j.Logger;
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.Identity;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.i18n.I18nManager;
+import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
+import org.olat.course.assessment.AssessmentHelper;
+import org.olat.course.nodes.CourseNode;
+import org.olat.course.nodes.IQTESTCourseNode;
+import org.olat.course.run.environment.CourseEnvironment;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.ims.qti21.AssessmentTestSession;
+import org.olat.ims.qti21.QTI21Service;
+import org.olat.ims.qti21.model.DigitalSignatureOptions;
+import org.olat.modules.assessment.Role;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryService;
+
+/**
+ * 
+ * Initial date: 16 oct. 2020<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class PullTestSessionsTask extends TimerTask implements Serializable {
+	
+	private static final Logger log = Tracing.createLoggerFor(PullTestSessionsTask.class);
+
+	private static final long serialVersionUID = 3863367666724686544L;
+	
+	private Long coachkKey;
+	private Long courseEntryKey;
+	private List<Long> testSessionKeys;
+	
+	public PullTestSessionsTask(Long courseEntryKey, List<Long> testSessionKeys, Long coachkKey) {
+		this.courseEntryKey = courseEntryKey;
+		this.testSessionKeys = testSessionKeys;
+		this.coachkKey = coachkKey;
+	}
+
+	@Override
+	public void run() {
+		RepositoryEntry courseEntry = CoreSpringFactory.getImpl(RepositoryService.class)
+				.loadByKey(courseEntryKey);
+		ICourse course = CourseFactory.loadCourse(courseEntry);
+		
+		DB dbInstance = CoreSpringFactory.getImpl(DB.class);
+		for(Long testSessionKey:testSessionKeys) {
+			try {
+				pullSession(course, testSessionKey);
+			} catch (Exception e) {
+				log.error("", e);
+			} finally {
+				dbInstance.commitAndCloseSession();
+			}
+		}
+	}
+	
+	private void pullSession(ICourse course, Long testSessionKey) {
+		QTI21Service qtiService = CoreSpringFactory.getImpl(QTI21Service.class);
+		AssessmentTestSession session = qtiService.getAssessmentTestSession(testSessionKey);
+		if(session == null || session.isCancelled() || session.isExploded()
+				|| session.getFinishTime() != null || session.getTerminationTime() != null) {
+			return;
+		}
+		
+		Identity identity = session.getIdentity();
+		CourseNode node = course.getRunStructure().getNode(session.getSubIdent());
+		if(node instanceof IQTESTCourseNode) {
+			Object identifier = identity == null ? session.getAnonymousIdentifier() : identity.getKey();
+			log.info(Tracing.M_AUDIT, "Retrieve test session async: {} (assessed identity={}) retrieved by coach {}",
+					session.getKey(), identifier, coachkKey);
+
+			IQTESTCourseNode courseNode = (IQTESTCourseNode)node;
+			String language = null;
+			if(identity != null) {
+				language = identity.getUser().getPreferences().getLanguage();
+			}
+			Locale locale = CoreSpringFactory.getImpl(I18nManager.class).getLocaleOrDefault(language);
+			DigitalSignatureOptions signatureOptions = courseNode.getSignatureOptions(session, locale);
+			session = qtiService.pullSession(session, signatureOptions, identity);
+
+			RepositoryEntry courseEntry = session.getRepositoryEntry();
+			CourseEnvironment courseEnv = CourseFactory.loadCourse(courseEntry).getCourseEnvironment();
+			UserCourseEnvironment assessedUserCourseEnv = AssessmentHelper
+					.createAndInitUserCourseEnvironment(session.getIdentity(), courseEnv);
+			courseNode.pullAssessmentTestSession(session, assessedUserCourseEnv, identity, Role.coach);
+		}
+	}
+}
diff --git a/src/main/java/org/olat/course/assessment/ui/tool/ConfirmStopAssessmentModeController.java b/src/main/java/org/olat/course/assessment/ui/tool/ConfirmStopAssessmentModeController.java
index 3e91dd4991661903785c2a7f8d01309ead96d47f..0b9b69b3656e974a7dbada9fc8a10febaf2e3f6c 100644
--- a/src/main/java/org/olat/course/assessment/ui/tool/ConfirmStopAssessmentModeController.java
+++ b/src/main/java/org/olat/course/assessment/ui/tool/ConfirmStopAssessmentModeController.java
@@ -89,7 +89,7 @@ public class ConfirmStopAssessmentModeController extends FormBasicController {
 			initForm(formLayout, nodeList, assessedIdentityKeys);
 		}
 		
-		if(runningSessions && false) {//TODO assessment mode
+		if(runningSessions) {
 			KeyValues keyValues = new KeyValues();
 			keyValues.add(KeyValues.entry("with", translate("confirm.stop.pull.running.sessions")));
 			pullRunningSessionsEl = uifactory.addCheckboxesHorizontal("runningSessions", "confirm.stop.pull.running.sessions", formLayout,
@@ -160,7 +160,7 @@ public class ConfirmStopAssessmentModeController extends FormBasicController {
 		AssessmentMode reloadedMode = assessmentModeManager.getAssessmentModeById(mode.getKey());
 		boolean pullTests = pullRunningSessionsEl != null && pullRunningSessionsEl.isAtLeastSelected(1);
 		boolean withDisadvantaged = withDisadvantagesEl == null || withDisadvantagesEl.isAtLeastSelected(1);
-		assessmentModeCoordinationService.stopAssessment(reloadedMode, pullTests, withDisadvantaged);
+		assessmentModeCoordinationService.stopAssessment(reloadedMode, pullTests, withDisadvantaged, getIdentity());
 		dbInstance.commit();
 		fireEvent(ureq, Event.DONE_EVENT);
 	}
diff --git a/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java b/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java
index 64ff12f0289499e2b17183e3b86bf9b56c3d3da3..f73cfd288b136dcff003920c580ec045fd1a639a 100644
--- a/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/IQTESTCourseNode.java
@@ -54,6 +54,7 @@ import org.olat.core.util.Util;
 import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.core.util.nodes.INode;
 import org.olat.core.util.resource.OresHelper;
+import org.olat.course.CourseFactory;
 import org.olat.course.ICourse;
 import org.olat.course.archiver.ScoreAccountingHelper;
 import org.olat.course.assessment.AssessmentManager;
@@ -107,6 +108,7 @@ import org.olat.ims.qti21.QTI21Module;
 import org.olat.ims.qti21.QTI21Service;
 import org.olat.ims.qti21.manager.AssessmentTestSessionDAO;
 import org.olat.ims.qti21.manager.archive.QTI21ArchiveFormat;
+import org.olat.ims.qti21.model.DigitalSignatureOptions;
 import org.olat.ims.qti21.model.QTI21StatisticSearchParams;
 import org.olat.ims.qti21.model.xml.QtiNodesExtractor;
 import org.olat.ims.qti21.resultexport.QTI21ResultsExportMediaResource;
@@ -271,6 +273,26 @@ public class IQTESTCourseNode extends AbstractAccessableCourseNode implements QT
 		}
 		return timeLimit;
 	}
+
+	public DigitalSignatureOptions getSignatureOptions(AssessmentTestSession session, Locale locale) {
+		RepositoryEntry testEntry = session.getTestEntry();
+		RepositoryEntry courseEntry = session.getRepositoryEntry();
+		QTI21DeliveryOptions deliveryOptions = CoreSpringFactory.getImpl(QTI21Service.class)
+				.getDeliveryOptions(testEntry);
+		
+		ModuleConfiguration config = getModuleConfiguration();
+		boolean digitalSignature = config.getBooleanSafe(IQEditController.CONFIG_DIGITAL_SIGNATURE,
+			deliveryOptions.isDigitalSignature());
+		boolean sendMail = config.getBooleanSafe(IQEditController.CONFIG_DIGITAL_SIGNATURE_SEND_MAIL,
+			deliveryOptions.isDigitalSignatureMail());
+
+		DigitalSignatureOptions options = new DigitalSignatureOptions(digitalSignature, sendMail, courseEntry, testEntry);
+		if(digitalSignature) {
+			CourseEnvironment courseEnv = CourseFactory.loadCourse(courseEntry).getCourseEnvironment();
+			QTI21AssessmentRunController.decorateCourseConfirmation(session, options, courseEnv, this, testEntry, null, locale);
+		}
+		return options;
+	}
 	
 	public boolean isScoreVisibleAfterCorrection() {
 		String defVisibility = CoreSpringFactory.getImpl(QTI21Module.class).isResultsVisibleAfterCorrectionWorkflow()
diff --git a/src/main/java/org/olat/ims/qti21/QTI21Service.java b/src/main/java/org/olat/ims/qti21/QTI21Service.java
index 81835ca50d5b6daa20b6f79797e329a2d5504a9e..90ce5b5ad01d7370bc9921cc3642a1fced5a7183 100644
--- a/src/main/java/org/olat/ims/qti21/QTI21Service.java
+++ b/src/main/java/org/olat/ims/qti21/QTI21Service.java
@@ -314,7 +314,7 @@ public interface QTI21Service {
 	 */
 	public AssessmentTestSession reopenAssessmentTestSession(AssessmentTestSession session, Identity actor);
 	
-	public List<AssessmentTestSession> getRunningAssessmentTestSession(RepositoryEntry entry, String subIdent, RepositoryEntry testEntry);
+	public List<AssessmentTestSession> getRunningAssessmentTestSession(RepositoryEntryRef entry, String subIdent, RepositoryEntry testEntry);
 	
 	public TestSessionState loadTestSessionState(AssessmentTestSession session);
 	
diff --git a/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java b/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java
index ff74f2f7bb498b8a896723a1155dcc40c892c695..6dc6cf4f41dec7d84556f2092f9bf90b850e6d70 100644
--- a/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java
+++ b/src/main/java/org/olat/ims/qti21/manager/AssessmentTestSessionDAO.java
@@ -514,6 +514,37 @@ public class AssessmentTestSessionDAO {
 		return query.getResultList();
 	}
 	
+	public List<AssessmentTestSession> getRunningTestSessions(RepositoryEntryRef entry, List<String> courseSubIdents, List<? extends IdentityRef> identities) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select session from qtiassessmenttestsession session")
+		  .append(" left join session.testEntry testEntry")
+		  .append(" left join testEntry.olatResource testResource")
+		  .append(" inner join fetch session.identity assessedIdentity")
+		  .append(" inner join fetch assessedIdentity.user assessedUser")
+		  .append(" where session.repositoryEntry.key=:repositoryEntryKey")
+		  .append(" and session.finishTime is null and session.terminationTime is null")
+		  .append(" and session.exploded=false and session.cancelled=false")
+		  .append(" and session.identity.key in (:identityKeys)");
+		if(courseSubIdents != null && !courseSubIdents.isEmpty()) {
+			sb.append(" and session.subIdent in (:subIdents)");
+		}
+		
+		List<Long> identityKeys = identities.stream()
+				.map(IdentityRef::getKey)
+				.collect(Collectors.toList());
+		
+		TypedQuery<AssessmentTestSession> query = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), AssessmentTestSession.class)
+				.setFirstResult(0)
+				.setMaxResults(1)
+				.setParameter("repositoryEntryKey", entry.getKey())
+				.setParameter("identityKeys", identityKeys);		
+		if(courseSubIdents != null && !courseSubIdents.isEmpty()) {
+			query.setParameter("subIdents", courseSubIdents);
+		}
+		return query.getResultList();
+	}
+	
 	/**
 	 * 
 	 * @param entry The repository entry (typically the course, or the test if not in a course) (mandatory)
diff --git a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java
index 93508b8d62e40c8b7e4c92c6293b7127b82112fa..2079d993ebc3fa96e0eba08b80e9aafedd893a36 100644
--- a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java
+++ b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java
@@ -351,9 +351,8 @@ public class QTI21ServiceImpl implements QTI21Service, UserDataDeletable, Initia
 			assessmentTestsCache.replace(resourceFile, resolvedAssessmentTest);
 			return resolvedAssessmentTest;
 		}
-		return assessmentTestsCache.computeIfAbsent(resourceFile, file -> {
-	        return internalLoadAndResolveAssessmentTest(resourceDirectory, assessmentObjectSystemId);
-		});
+		return assessmentTestsCache.computeIfAbsent(resourceFile, file ->
+	        internalLoadAndResolveAssessmentTest(resourceDirectory, assessmentObjectSystemId));
 	}
 	
 	private ResolvedAssessmentTest internalLoadAndResolveAssessmentTest(File resourceDirectory, URI assessmentObjectSystemId) {
@@ -368,9 +367,8 @@ public class QTI21ServiceImpl implements QTI21Service, UserDataDeletable, Initia
 	@Override
 	public ResolvedAssessmentItem loadAndResolveAssessmentItem(URI assessmentObjectSystemId, File resourceDirectory) {
 		File resourceFile = new File(assessmentObjectSystemId);
-		return assessmentItemsCache.computeIfAbsent(resourceFile, (file) -> {
-	       	return loadAndResolveAssessmentItemForCopy(assessmentObjectSystemId, resourceDirectory);
-		});
+		return assessmentItemsCache.computeIfAbsent(resourceFile, file -> 
+	       	loadAndResolveAssessmentItemForCopy(assessmentObjectSystemId, resourceDirectory));
 	}
 	
 	@Override
@@ -667,7 +665,7 @@ public class QTI21ServiceImpl implements QTI21Service, UserDataDeletable, Initia
 	}
 
 	@Override
-	public List<AssessmentTestSession> getRunningAssessmentTestSession(RepositoryEntry entry, String subIdent, RepositoryEntry testEntry) {
+	public List<AssessmentTestSession> getRunningAssessmentTestSession(RepositoryEntryRef entry, String subIdent, RepositoryEntry testEntry) {
 		return testSessionDao.getRunningTestSessions(entry, subIdent, testEntry);
 	}
 
@@ -909,7 +907,7 @@ public class QTI21ServiceImpl implements QTI21Service, UserDataDeletable, Initia
 				return new DigitalSignatureValidation(DigitalSignatureValidation.Message.sessionNotFound, false);
 			}
 			String testSessionKey = uri.substring(start + 1, end);
-			AssessmentTestSession testSession = getAssessmentTestSession(new Long(testSessionKey));
+			AssessmentTestSession testSession = getAssessmentTestSession(Long.valueOf(testSessionKey));
 			if(testSession == null) {
 				return new DigitalSignatureValidation(DigitalSignatureValidation.Message.sessionNotFound, false);
 			}
@@ -1245,7 +1243,7 @@ public class QTI21ServiceImpl implements QTI21Service, UserDataDeletable, Initia
 		try {
 			Value computedValue = outcomeVariable.getComputedValue();
 			if (QtiConstants.VARIABLE_DURATION_IDENTIFIER.equals(identifier)) {
-				log.info(Tracing.M_AUDIT, candidateSession.getKey() + " :: " + outcomeVariable.getIdentifier() + " - " + stringifyQtiValue(computedValue));
+				log.info(Tracing.M_AUDIT, "{} :: {} - {}", candidateSession.getKey(), outcomeVariable.getIdentifier(), stringifyQtiValue(computedValue));
 			} else if (QTI21Constants.SCORE_IDENTIFIER.equals(identifier)) {
 				if (computedValue instanceof NumberValue) {
 					double score = ((NumberValue) computedValue).doubleValue();