msgs) {
+ return Mono.error(new UnsupportedOperationException("not used"));
+ }
}
}
diff --git a/agentscope-core/src/test/java/io/opentelemetry/api/trace/Span.java b/agentscope-core/src/test/java/io/opentelemetry/api/trace/Span.java
new file mode 100644
index 000000000..5ccc455d0
--- /dev/null
+++ b/agentscope-core/src/test/java/io/opentelemetry/api/trace/Span.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2024-2026 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.opentelemetry.api.trace;
+
+/**
+ * Minimal OpenTelemetry stubs for tests.
+ *
+ * AgentScope core does not depend on OpenTelemetry directly. JsonlTraceExporter uses reflection
+ * to attach trace_id/span_id when OpenTelemetry is present at runtime. These stubs let us cover
+ * that branch in unit tests without adding a core dependency.
+ */
+public final class Span {
+
+ private static volatile Span current = new Span(new SpanContext(false, "", ""));
+
+ private final SpanContext context;
+
+ public Span(SpanContext context) {
+ this.context = context;
+ }
+
+ public static Span current() {
+ return current;
+ }
+
+ public static void setCurrent(Span span) {
+ current = span;
+ }
+
+ public SpanContext getSpanContext() {
+ return context;
+ }
+}
diff --git a/agentscope-core/src/test/java/io/opentelemetry/api/trace/SpanContext.java b/agentscope-core/src/test/java/io/opentelemetry/api/trace/SpanContext.java
new file mode 100644
index 000000000..de1ee35ba
--- /dev/null
+++ b/agentscope-core/src/test/java/io/opentelemetry/api/trace/SpanContext.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024-2026 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.opentelemetry.api.trace;
+
+/** Minimal OpenTelemetry stub for tests. */
+public final class SpanContext {
+ private final boolean valid;
+ private final String traceId;
+ private final String spanId;
+
+ public SpanContext(boolean valid, String traceId, String spanId) {
+ this.valid = valid;
+ this.traceId = traceId;
+ this.spanId = spanId;
+ }
+
+ public boolean isValid() {
+ return valid;
+ }
+
+ public String getTraceId() {
+ return traceId;
+ }
+
+ public String getSpanId() {
+ return spanId;
+ }
+}
From 51a51493dd1243a23312be39dc50bbfc9ba91fe5 Mon Sep 17 00:00:00 2001
From: yzy <1248814210@qq.com>
Date: Mon, 23 Mar 2026 18:38:10 +0800
Subject: [PATCH 3/7] ci: reinstall mvnd if missing in Linux job
---
.github/workflows/maven-ci.yml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/.github/workflows/maven-ci.yml b/.github/workflows/maven-ci.yml
index ad6433264..9d24e910a 100644
--- a/.github/workflows/maven-ci.yml
+++ b/.github/workflows/maven-ci.yml
@@ -109,8 +109,16 @@ jobs:
- name: Build and Test with Coverage [Linux]
if: runner.os == 'Linux'
+ shell: bash
run: |
export PATH="$HOME/.mvnd/bin:$PATH"
+ if ! command -v mvnd >/dev/null 2>&1; then
+ echo "mvnd not found, installing..."
+ curl -fsSL https://github.com/apache/maven-mvnd/releases/download/${{ env.MVND_VERSION }}/maven-mvnd-${{ env.MVND_VERSION }}-linux-amd64.tar.gz | tar xz
+ mkdir -p ~/.mvnd
+ mv maven-mvnd-${{ env.MVND_VERSION }}-linux-amd64/* ~/.mvnd/
+ export PATH="$HOME/.mvnd/bin:$PATH"
+ fi
# -T1: single-threaded build so reactor order is respected and agentscope-core
# is installed before modules (e.g. subagent) that resolve it from the reactor.
mvnd -B -T1 clean verify
From bb4f3c2eb20c6c977697dbb8656590530e10add9 Mon Sep 17 00:00:00 2001
From: yzy <1248814210@qq.com>
Date: Mon, 23 Mar 2026 19:30:00 +0800
Subject: [PATCH 4/7] test: implement StreamableAgent schema stream stub
---
.../core/hook/recorder/JsonlTraceExporterTest.java | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/agentscope-core/src/test/java/io/agentscope/core/hook/recorder/JsonlTraceExporterTest.java b/agentscope-core/src/test/java/io/agentscope/core/hook/recorder/JsonlTraceExporterTest.java
index 563d980df..b70293740 100644
--- a/agentscope-core/src/test/java/io/agentscope/core/hook/recorder/JsonlTraceExporterTest.java
+++ b/agentscope-core/src/test/java/io/agentscope/core/hook/recorder/JsonlTraceExporterTest.java
@@ -332,6 +332,11 @@ public Flux stream(List msgs, StreamOptions options, Class> struct
return Flux.error(new UnsupportedOperationException("not used"));
}
+ @Override
+ public Flux stream(List msgs, StreamOptions options, JsonNode schema) {
+ return Flux.error(new UnsupportedOperationException("not used"));
+ }
+
@Override
public Mono observe(Msg msg) {
return Mono.error(new UnsupportedOperationException("not used"));
From bfb6d6a0702c9565505209108c1404b2877b8a20 Mon Sep 17 00:00:00 2001
From: yzy <1248814210@qq.com>
Date: Wed, 25 Mar 2026 13:00:13 +0800
Subject: [PATCH 5/7] Fix JSONL trace exporter review issues
---
agentscope-core/pom.xml | 6 +
.../hook/recorder/JsonlTraceExporter.java | 234 ++++++++++++++----
.../hook/recorder/JsonlTraceExporterTest.java | 146 ++++++++++-
.../java/io/opentelemetry/api/trace/Span.java | 46 ----
.../opentelemetry/api/trace/SpanContext.java | 41 ---
docs/en/task/hook.md | 4 +
docs/zh/task/hook.md | 4 +-
7 files changed, 333 insertions(+), 148 deletions(-)
delete mode 100644 agentscope-core/src/test/java/io/opentelemetry/api/trace/Span.java
delete mode 100644 agentscope-core/src/test/java/io/opentelemetry/api/trace/SpanContext.java
diff --git a/agentscope-core/pom.xml b/agentscope-core/pom.xml
index 54bf500a7..b5bb9a12b 100644
--- a/agentscope-core/pom.xml
+++ b/agentscope-core/pom.xml
@@ -140,5 +140,11 @@
com.networknt
json-schema-validator
+
+
+ io.opentelemetry
+ opentelemetry-api
+ test
+
diff --git a/agentscope-core/src/main/java/io/agentscope/core/hook/recorder/JsonlTraceExporter.java b/agentscope-core/src/main/java/io/agentscope/core/hook/recorder/JsonlTraceExporter.java
index 9054e5d50..bd9ec84ff 100644
--- a/agentscope-core/src/main/java/io/agentscope/core/hook/recorder/JsonlTraceExporter.java
+++ b/agentscope-core/src/main/java/io/agentscope/core/hook/recorder/JsonlTraceExporter.java
@@ -36,6 +36,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.OpenOption;
@@ -47,12 +48,21 @@
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.WeakHashMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
-import reactor.core.scheduler.Schedulers;
/**
* A built-in, out-of-the-box JSONL trace exporter based on the Hook event system.
@@ -65,13 +75,15 @@
*
* - This exporter is best-effort by default: serialization / IO errors do not break agent
* execution unless {@link Builder#failFast(boolean)} is enabled.
- * - This exporter performs blocking file IO, but it runs on Reactor boundedElastic to avoid
- * blocking agent execution threads.
+ * - This exporter performs blocking file IO on an internal single-threaded queue to keep file
+ * order, step IDs, and run IDs consistent.
*
*/
public final class JsonlTraceExporter implements Hook, AutoCloseable {
private static final Logger log = LoggerFactory.getLogger(JsonlTraceExporter.class);
+ private static final long CLOSE_TIMEOUT_SECONDS = 30L;
+ private static final OpenTelemetryAccess OPEN_TELEMETRY_ACCESS = OpenTelemetryAccess.create();
private final Path outputFile;
private final boolean flushEveryLine;
@@ -81,8 +93,10 @@ public final class JsonlTraceExporter implements Hook, AutoCloseable {
private final Object lock = new Object();
private final BufferedWriter writer;
+ private final ExecutorService exportExecutor;
+ private final AtomicBoolean closed = new AtomicBoolean(false);
- private final Map runStates = new ConcurrentHashMap<>();
+ private final Map runStates = new WeakHashMap<>();
private JsonlTraceExporter(
Path outputFile,
@@ -97,6 +111,7 @@ private JsonlTraceExporter(
this.priority = priority;
this.eventFilter = Objects.requireNonNull(eventFilter, "eventFilter cannot be null");
this.writer = openWriter(outputFile, append);
+ this.exportExecutor = createExportExecutor();
}
public static Builder builder(Path outputFile) {
@@ -110,27 +125,50 @@ public int priority() {
@Override
public Mono onEvent(T event) {
- if (event == null || !eventFilter.test(event)) {
- return Mono.just(event);
+ T nonNullEvent = Objects.requireNonNull(event, "event cannot be null");
+ if (!eventFilter.test(nonNullEvent)) {
+ return Mono.just(nonNullEvent);
}
- return Mono.fromCallable(
- () -> {
- writeEvent(event);
- return event;
- })
- .subscribeOn(Schedulers.boundedElastic())
+ return Mono.defer(() -> enqueueWrite(nonNullEvent, OPEN_TELEMETRY_ACCESS.captureCurrent()))
.onErrorResume(
- e -> {
+ error -> {
if (failFast) {
- return Mono.error(e);
+ return Mono.error(error);
}
- log.warn("Failed to export hook event to JSONL: {}", e.getMessage(), e);
- return Mono.just(event);
+ log.warn(
+ "Failed to export hook event to JSONL: {}",
+ error.getMessage(),
+ error);
+ return Mono.just(nonNullEvent);
});
}
- private void writeEvent(HookEvent event) throws IOException {
+ private Mono enqueueWrite(T event, OpenTelemetryIds openTelemetryIds) {
+ if (closed.get()) {
+ return Mono.error(
+ new RejectedExecutionException(
+ "JSONL exporter is closed: " + outputFile.toAbsolutePath()));
+ }
+
+ CompletableFuture future = new CompletableFuture<>();
+ try {
+ exportExecutor.execute(
+ () -> {
+ try {
+ writeEvent(event, openTelemetryIds);
+ future.complete(event);
+ } catch (Throwable error) {
+ future.completeExceptionally(error);
+ }
+ });
+ } catch (RejectedExecutionException error) {
+ future.completeExceptionally(error);
+ }
+ return Mono.fromFuture(future);
+ }
+
+ private void writeEvent(HookEvent event, OpenTelemetryIds openTelemetryIds) throws IOException {
RunState runState = getOrUpdateRunState(event);
Map record = new LinkedHashMap<>();
@@ -143,14 +181,15 @@ private void writeEvent(HookEvent event) throws IOException {
record.put("turn_id", runState.turnId);
record.put("step_id", runState.stepId);
- putOpenTelemetryIdsIfPresent(record);
+ if (openTelemetryIds != null) {
+ openTelemetryIds.putIfPresent(record);
+ }
if (event instanceof ReasoningEvent reasoningEvent) {
record.put("model_name", reasoningEvent.getModelName());
record.put("generate_options", reasoningEvent.getGenerateOptions());
}
- // Payload
if (event instanceof PreCallEvent e) {
record.put("input_messages", e.getInputMessages());
} else if (event instanceof PostCallEvent e) {
@@ -172,6 +211,7 @@ private void writeEvent(HookEvent event) throws IOException {
record.put("tool_use", e.getToolUse());
} else if (event instanceof ActingChunkEvent e) {
record.put("tool_use", e.getToolUse());
+ record.put("incremental_chunk", e.getChunk());
record.put("chunk", e.getChunk());
} else if (event instanceof PostActingEvent e) {
record.put("tool_use", e.getToolUse());
@@ -251,41 +291,50 @@ private static String stackTraceToString(Throwable error) {
return sw.toString();
}
- private static void putOpenTelemetryIdsIfPresent(Map record) {
- // Optional integration: if OpenTelemetry is on the classpath, try to attach trace/span id.
- // This keeps core module free of hard dependencies on OpenTelemetry.
+ @Override
+ public void close() throws IOException {
+ if (!closed.compareAndSet(false, true)) {
+ return;
+ }
+
+ boolean drained = false;
try {
- Class> spanClass = Class.forName("io.opentelemetry.api.trace.Span");
- Object span = spanClass.getMethod("current").invoke(null);
- if (span == null) {
- return;
- }
- Object spanContext = spanClass.getMethod("getSpanContext").invoke(span);
- if (spanContext == null) {
- return;
+ Future> barrier = exportExecutor.submit(() -> {});
+ barrier.get(CLOSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ drained = true;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IOException(
+ "Interrupted while waiting for JSONL exporter to finish pending writes", e);
+ } catch (TimeoutException e) {
+ throw new IOException(
+ "Timed out while waiting for JSONL exporter to finish pending writes", e);
+ } catch (ExecutionException e) {
+ throw new IOException(
+ "Failed while waiting for JSONL exporter to finish pending writes",
+ e.getCause());
+ } finally {
+ if (drained) {
+ exportExecutor.shutdown();
+ } else {
+ exportExecutor.shutdownNow();
}
-
- Class> spanContextClass = Class.forName("io.opentelemetry.api.trace.SpanContext");
- boolean valid = (boolean) spanContextClass.getMethod("isValid").invoke(spanContext);
- if (!valid) {
- return;
+ runStates.clear();
+ synchronized (lock) {
+ writer.flush();
+ writer.close();
}
-
- String traceId = (String) spanContextClass.getMethod("getTraceId").invoke(spanContext);
- String spanId = (String) spanContextClass.getMethod("getSpanId").invoke(spanContext);
- record.put("trace_id", traceId);
- record.put("span_id", spanId);
- } catch (Throwable ignored) {
- // Ignore all reflection failures.
}
}
- @Override
- public void close() throws IOException {
- synchronized (lock) {
- writer.flush();
- writer.close();
- }
+ private static ExecutorService createExportExecutor() {
+ ThreadFactory threadFactory =
+ runnable -> {
+ Thread thread = new Thread(runnable, "agentscope-jsonl-trace-exporter");
+ thread.setDaemon(true);
+ return thread;
+ };
+ return Executors.newSingleThreadExecutor(threadFactory);
}
private static final class RunState {
@@ -294,6 +343,93 @@ private static final class RunState {
private long stepId = 0;
}
+ private static final class OpenTelemetryAccess {
+ private final Method currentMethod;
+ private final Method getSpanContextMethod;
+ private final Method isValidMethod;
+ private final Method getTraceIdMethod;
+ private final Method getSpanIdMethod;
+
+ private OpenTelemetryAccess(
+ Method currentMethod,
+ Method getSpanContextMethod,
+ Method isValidMethod,
+ Method getTraceIdMethod,
+ Method getSpanIdMethod) {
+ this.currentMethod = currentMethod;
+ this.getSpanContextMethod = getSpanContextMethod;
+ this.isValidMethod = isValidMethod;
+ this.getTraceIdMethod = getTraceIdMethod;
+ this.getSpanIdMethod = getSpanIdMethod;
+ }
+
+ private static OpenTelemetryAccess create() {
+ try {
+ ClassLoader classLoader = JsonlTraceExporter.class.getClassLoader();
+ Class> spanClass =
+ Class.forName("io.opentelemetry.api.trace.Span", false, classLoader);
+ Class> spanContextClass =
+ Class.forName("io.opentelemetry.api.trace.SpanContext", false, classLoader);
+ return new OpenTelemetryAccess(
+ spanClass.getMethod("current"),
+ spanClass.getMethod("getSpanContext"),
+ spanContextClass.getMethod("isValid"),
+ spanContextClass.getMethod("getTraceId"),
+ spanContextClass.getMethod("getSpanId"));
+ } catch (Throwable ignored) {
+ return new OpenTelemetryAccess(null, null, null, null, null);
+ }
+ }
+
+ private void putIfPresent(Map record) {
+ if (currentMethod == null) {
+ return;
+ }
+ OpenTelemetryIds openTelemetryIds = captureCurrent();
+ if (openTelemetryIds != null) {
+ openTelemetryIds.putIfPresent(record);
+ }
+ }
+
+ private OpenTelemetryIds captureCurrent() {
+ if (currentMethod == null) {
+ return null;
+ }
+ try {
+ Object span = currentMethod.invoke(null);
+ if (span == null) {
+ return null;
+ }
+ Object spanContext = getSpanContextMethod.invoke(span);
+ if (spanContext == null || !(boolean) isValidMethod.invoke(spanContext)) {
+ return null;
+ }
+ return new OpenTelemetryIds(
+ (String) getTraceIdMethod.invoke(spanContext),
+ (String) getSpanIdMethod.invoke(spanContext));
+ } catch (Throwable ignored) {
+ return null;
+ }
+ }
+ }
+
+ private static final class OpenTelemetryIds {
+ private final String traceId;
+ private final String spanId;
+
+ private OpenTelemetryIds(String traceId, String spanId) {
+ this.traceId = traceId;
+ this.spanId = spanId;
+ }
+
+ private void putIfPresent(Map record) {
+ if (traceId != null && spanId != null) {
+ record.put("trace_id", traceId);
+ record.put("span_id", spanId);
+ }
+ }
+ }
+
public static final class Builder {
private final Path outputFile;
diff --git a/agentscope-core/src/test/java/io/agentscope/core/hook/recorder/JsonlTraceExporterTest.java b/agentscope-core/src/test/java/io/agentscope/core/hook/recorder/JsonlTraceExporterTest.java
index b70293740..68b805621 100644
--- a/agentscope-core/src/test/java/io/agentscope/core/hook/recorder/JsonlTraceExporterTest.java
+++ b/agentscope-core/src/test/java/io/agentscope/core/hook/recorder/JsonlTraceExporterTest.java
@@ -47,12 +47,24 @@
import io.agentscope.core.model.GenerateOptions;
import io.agentscope.core.tool.Toolkit;
import io.agentscope.core.util.JsonUtils;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanContext;
+import io.opentelemetry.api.trace.TraceFlags;
+import io.opentelemetry.api.trace.TraceState;
+import io.opentelemetry.context.Scope;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import reactor.core.publisher.Flux;
@@ -150,6 +162,13 @@ void writesJsonlLinesForEnabledEvents() throws Exception {
assertTrue(containsKeyValue(records, "event_type", "POST_SUMMARY"));
assertTrue(containsKeyValue(records, "event_type", "ERROR"));
assertTrue(containsKeyValue(records, "event_type", "POST_CALL"));
+
+ Map actingChunkRecord = findByEventType(records, "ACTING_CHUNK");
+ assertNotNull(actingChunkRecord.get("incremental_chunk"));
+ assertNotNull(actingChunkRecord.get("chunk"));
+ assertEquals(
+ JsonUtils.getJsonCodec().toJson(actingChunkRecord.get("incremental_chunk")),
+ JsonUtils.getJsonCodec().toJson(actingChunkRecord.get("chunk")));
}
@Test
@@ -190,6 +209,8 @@ void runIdChangesAcrossTurns() throws Exception {
assertNotNull(run1);
assertNotNull(run2);
assertNotEquals(run1, run2);
+ assertEquals(1, ((Number) records.get(0).get("turn_id")).intValue());
+ assertEquals(2, ((Number) records.get(2).get("turn_id")).intValue());
}
@Test
@@ -223,22 +244,119 @@ void failFastControlsErrorPropagation() throws Exception {
}
@Test
- void exportsOpenTelemetryIdsWhenAvailable() throws Exception {
- io.opentelemetry.api.trace.Span.setCurrent(
- new io.opentelemetry.api.trace.Span(
- new io.opentelemetry.api.trace.SpanContext(true, "trace-abc", "span-xyz")));
+ void rejectsNullEvents() throws Exception {
+ Path output = tempDir.resolve("null.jsonl");
+ try (JsonlTraceExporter exporter =
+ JsonlTraceExporter.builder(output).append(false).flushEveryLine(true).build()) {
+ assertThrows(NullPointerException.class, () -> exporter.onEvent(null));
+ }
+ }
- Path output = tempDir.resolve("otel.jsonl");
+ @Test
+ void serializesConcurrentExportsPerAgent() throws Exception {
+ Path output = tempDir.resolve("concurrent.jsonl");
TestAgent agent = new TestAgent("agent-1", "TestAgent");
+ Msg assistantMsg = textMsg(MsgRole.ASSISTANT, "world");
+ GenerateOptions options = GenerateOptions.builder().build();
+ int eventCount = 200;
+
+ ExecutorService executor = Executors.newFixedThreadPool(32);
+ CountDownLatch start = new CountDownLatch(1);
+ CountDownLatch done = new CountDownLatch(eventCount);
+ ConcurrentLinkedQueue failures = new ConcurrentLinkedQueue<>();
try (JsonlTraceExporter exporter =
JsonlTraceExporter.builder(output).append(false).flushEveryLine(true).build()) {
exporter.onEvent(new PreCallEvent(agent, List.of(textMsg(MsgRole.USER, "hi")))).block();
+
+ for (int i = 0; i < eventCount; i++) {
+ executor.submit(
+ () -> {
+ try {
+ assertTrue(start.await(30, TimeUnit.SECONDS));
+ exporter.onEvent(
+ new PostReasoningEvent(
+ agent, "mock-model", options, assistantMsg))
+ .block();
+ } catch (Throwable t) {
+ failures.add(t);
+ } finally {
+ done.countDown();
+ }
+ });
+ }
+
+ start.countDown();
+ assertTrue(done.await(30, TimeUnit.SECONDS));
+ assertTrue(failures.isEmpty(), () -> "Unexpected failures: " + failures);
+ } finally {
+ executor.shutdownNow();
+ }
+
+ List