diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile
index 395c12dd..e4b47ed6 100644
--- a/docker/app/Dockerfile
+++ b/docker/app/Dockerfile
@@ -16,7 +16,7 @@ ENV CI=true
 USER root
 WORKDIR /repo
 
-COPY --from=source-tree /repo/pnpm-lock.yaml /repo/pnpm-lock.yaml
+COPY --from=source-tree /repo/pnpm-lock.yaml /repo/.npmrc /repo/
 
 RUN pnpm fetch
 
@@ -40,6 +40,8 @@ WORKDIR /root
 
 COPY ./docker/app/root/scripts /root/scripts
 
+RUN mkdir workspace
+
 ENV SELF_PARTY=alice
 ENV ALL_PARTIES=alice
 
diff --git a/docker/app/root/scripts/start.sh b/docker/app/root/scripts/start.sh
index 4ddd2e68..a691ef24 100755
--- a/docker/app/root/scripts/start.sh
+++ b/docker/app/root/scripts/start.sh
@@ -7,4 +7,4 @@ ray start \
   --disable-usage-stats \
   --resources="{\"$SELF_PARTY\": 4}"
 
-secretnote --allow-root --no-browser
+secretnote --allow-root --no-browser --notebook-dir=/root/workspace
diff --git a/packages/secretnote/src/components/dropdown-menu/index.less b/packages/secretnote/src/components/dropdown-menu/index.less
index a31ab151..5189b430 100644
--- a/packages/secretnote/src/components/dropdown-menu/index.less
+++ b/packages/secretnote/src/components/dropdown-menu/index.less
@@ -31,6 +31,8 @@
   }
 
   .ant-upload {
+    padding-left: 30px;
+    margin-left: -30px;
     color: var(--mana-secretnote-text-color);
     font-size: 12px;
   }
diff --git a/packages/secretnote/src/modules/editor/cell/view.tsx b/packages/secretnote/src/modules/editor/cell/view.tsx
index 95dfe679..9ad1aaf2 100644
--- a/packages/secretnote/src/modules/editor/cell/view.tsx
+++ b/packages/secretnote/src/modules/editor/cell/view.tsx
@@ -10,7 +10,6 @@ import {
   KernelError,
   ILSPDocumentConnectionManager,
   CodeEditorManager,
-  CodeEditorSettings,
 } from '@difizen/libro-jupyter';
 import {
   getOrigin,
@@ -25,6 +24,7 @@ import {
 } from '@difizen/mana-app';
 import { l10n } from '@difizen/mana-l10n';
 import { message } from 'antd';
+import { isUndefined } from 'lodash-es';
 import { forwardRef } from 'react';
 
 import { Ribbon } from '@/components/ribbon';
@@ -36,15 +36,15 @@ import type { SecretNoteModel } from '../model';
 
 const SecretNoteCodeCellComponent = forwardRef<HTMLDivElement>((props, ref) => {
   const instance = useInject<SecretNoteCodeCellView>(ViewInstance);
-  const { allExecutionParty, executionParty } = instance;
+  const { partyList, parties } = instance;
 
   return (
     <div className={instance.className} ref={ref} tabIndex={10} onBlur={instance.blur}>
       <Ribbon
-        items={allExecutionParty}
-        value={executionParty}
+        items={partyList.map((name) => ({ label: name, key: name }))}
+        value={parties}
         onChange={(val) => {
-          instance.changeExecutionParty(val);
+          instance.onPartiesChange(val);
         }}
       >
         <CellEditorMemo />
@@ -54,6 +54,8 @@ const SecretNoteCodeCellComponent = forwardRef<HTMLDivElement>((props, ref) => {
 });
 SecretNoteCodeCellComponent.displayName = 'SecretNoteCodeCellComponent';
 
+let lastParties: string[] = []; // store last parties for new cell
+
 @transient()
 @view('secretnote-code-cell-view')
 export class SecretNoteCodeCellView extends JupyterCodeCellView {
@@ -63,13 +65,10 @@ export class SecretNoteCodeCellView extends JupyterCodeCellView {
   view = SecretNoteCodeCellComponent;
 
   @prop()
-  executionParty: string[] = [];
+  parties: string[] = [];
 
-  get allExecutionParty() {
-    return this.serverManager.servers.map((server) => ({
-      key: server.id,
-      label: server.name,
-    }));
+  get partyList() {
+    return this.serverManager.servers.map((server) => server.name);
   }
 
   constructor(
@@ -80,89 +79,72 @@ export class SecretNoteCodeCellView extends JupyterCodeCellView {
     @inject(SecretNoteKernelManager) kernelManager: SecretNoteKernelManager,
     @inject(ILSPDocumentConnectionManager) lsp: ILSPDocumentConnectionManager,
     @inject(CodeEditorManager) codeEditorManager: CodeEditorManager,
-    @inject(CodeEditorSettings) codeEditorSettings: CodeEditorSettings,
   ) {
-    super(
-      options,
-      cellService,
-      viewManager,
-      lsp,
-      codeEditorManager,
-      codeEditorSettings,
-    );
+    super(options, cellService, viewManager, lsp, codeEditorManager);
     this.serverManager = serverManager;
     this.kernelManager = kernelManager;
-    this.executionParty =
-      this.getExecutionParty() || this.allExecutionParty.map((item) => item.key);
+    this.parties = this.getInitializedParties();
   }
 
-  async run() {
+  getUsableConnections() {
     const libroModel = this.parent.model as SecretNoteModel;
-    const cellModel = this.model;
 
     if (!libroModel) {
-      return false;
+      return [];
     }
 
-    let kernelConnections = getOrigin(libroModel.kernelConnections);
-
-    // 没有可用的 Kernel 连接
-    if (kernelConnections.length === 0) {
-      message.info(l10n.t('没有可用的 Kernel 连接'));
-      return false;
-    }
+    const kernelConnections = getOrigin(libroModel.kernelConnections);
 
-    kernelConnections = kernelConnections.filter((connection) => {
+    return kernelConnections.filter((connection) => {
+      if (connection.isDisposed) {
+        return false;
+      }
       const server = this.kernelManager.getServerByKernelConnection(connection);
       return (
-        server && server.status === 'running' && this.executionParty.includes(server.id)
+        server && server.status === 'running' && this.parties.includes(server.name)
       );
     });
-    if (kernelConnections.length === 0) {
-      message.info(l10n.t('请选择一个执行节点'));
-      return false;
-    }
-    if (kernelConnections.length !== this.executionParty.length) {
-      message.info(l10n.t('有的 Kernel 连接不可用'));
-      return false;
-    }
+  }
 
-    const hasDisposedConnection = kernelConnections.some((item) => {
-      return item.isDisposed;
-    });
-    if (hasDisposedConnection) {
-      message.error(l10n.t('有的 Kernel 连接已经被销毁'));
+  async run() {
+    const cellModel = this.model;
+    const kernelConnections = this.getUsableConnections();
+
+    if (kernelConnections.length === 0) {
+      message.info(l10n.t('No available node to execute'));
       return false;
     }
 
     this.clearExecution();
-    this.setExecutionStatus({ executing: true });
-    this.setExecutionTime({ start: '', end: '', toExecute: new Date().toISOString() });
-    this.setExecutionParty();
+    this.updateExecutionStatus({ executing: true });
+    this.updateExecutionTime({
+      toExecute: new Date().toISOString(),
+    });
+    this.savePartiesToMeta();
 
     try {
       const list: Promise<KernelMessage.IExecuteReplyMsg>[] = [];
       for (let i = 0, len = kernelConnections.length; i < len; i += 1) {
         const connection = kernelConnections[i];
-
-        const future = connection.requestExecute({
-          code: cellModel.value,
-        });
+        const future = connection.requestExecute(
+          {
+            code: cellModel.value,
+          },
+          // Even after receiving a reply message, you can still receive other messages.
+          false,
+        );
 
         future.onIOPub = (
           msg: KernelMessage.IIOPubMessage<KernelMessage.IOPubMessageType>,
         ) => {
+          if (msg.header.msg_type === 'execute_input') {
+            this.updateExecutionStatus({ kernelExecuting: true });
+            this.updateExecutionTime({ start: msg.header.date });
+          }
           cellModel.msgChangeEmitter.fire({
             connection,
             msg,
           });
-          if (
-            cellModel.kernelExecuting === false &&
-            msg.header.msg_type === 'execute_input'
-          ) {
-            this.setExecutionStatus({ kernelExecuting: true });
-            this.setExecutionTime({ start: msg.header.date });
-          }
         };
 
         future.onReply = (msg: KernelMessage.IExecuteReplyMsg) => {
@@ -176,8 +158,8 @@ export class SecretNoteCodeCellView extends JupyterCodeCellView {
       }
 
       const futureDoneList = await Promise.all(list);
-      this.setExecutionStatus({ executing: false, kernelExecuting: false });
-      this.setExecutionTime(this.parseMessageTime(futureDoneList));
+      this.updateExecutionStatus({ executing: false, kernelExecuting: false });
+      this.updateExecutionTime(this.parseMessageTime(futureDoneList));
 
       const ok = futureDoneList.every((msg) => msg.content.status === 'ok');
       if (ok) {
@@ -197,27 +179,27 @@ export class SecretNoteCodeCellView extends JupyterCodeCellView {
     }
   }
 
-  setExecutionStatus(status: { executing?: boolean; kernelExecuting?: boolean }) {
+  updateExecutionStatus(status: { executing?: boolean; kernelExecuting?: boolean }) {
     const { executing, kernelExecuting } = status;
-    if (executing !== undefined) {
+    if (!isUndefined(executing)) {
       this.model.executing = executing;
     }
-    if (kernelExecuting !== undefined) {
+    if (!isUndefined(kernelExecuting)) {
       this.model.kernelExecuting = kernelExecuting;
     }
   }
 
-  setExecutionTime(times: { start?: string; end?: string; toExecute?: string }) {
+  updateExecutionTime(times: { start?: string; end?: string; toExecute?: string }) {
     const meta = this.model.metadata.execution as ExecutionMeta;
     if (meta) {
       const { start, end, toExecute } = times;
-      if (start !== undefined) {
+      if (!isUndefined(start)) {
         meta['shell.execute_reply.started'] = start;
       }
-      if (end !== undefined) {
+      if (!isUndefined(end)) {
         meta['shell.execute_reply.end'] = end;
       }
-      if (toExecute !== undefined) {
+      if (!isUndefined(toExecute)) {
         meta.to_execute = toExecute;
       }
     }
@@ -240,29 +222,32 @@ export class SecretNoteCodeCellView extends JupyterCodeCellView {
     return { start, end };
   }
 
-  changeExecutionParty(party: string[]) {
-    this.executionParty = party;
-    this.setExecutionParty(party);
+  onPartiesChange(parties: string[]) {
+    this.parties = parties;
+    this.savePartiesToMeta(parties);
+    lastParties = parties;
   }
 
-  getExecutionParty() {
+  getInitializedParties() {
     const execution = this.model.metadata.execution as ExecutionMeta;
-    if (execution && execution.executionParty) {
+    if (execution && execution.parties) {
       try {
-        const party: string[] = JSON.parse(execution.executionParty as string);
-        return party.filter((p) =>
-          this.allExecutionParty.some((item) => item.key === p),
-        );
+        const parties: string[] = JSON.parse(execution.parties as string);
+        return parties.filter((p) => this.partyList.includes(p));
       } catch (e) {
         return [];
       }
+    } else if (lastParties.length > 0) {
+      // load parties from previous cell settings
+      return lastParties;
     }
+    return this.partyList;
   }
 
-  setExecutionParty(party: string[] = this.executionParty) {
+  savePartiesToMeta(parties: string[] = this.parties) {
     const execution = this.model.metadata.execution as ExecutionMeta;
     if (execution) {
-      execution.executionParty = JSON.stringify(party);
+      execution.parties = JSON.stringify(parties);
     }
   }
 }
diff --git a/packages/secretnote/src/modules/editor/model.ts b/packages/secretnote/src/modules/editor/model.ts
index 5a0781cf..52a31b21 100644
--- a/packages/secretnote/src/modules/editor/model.ts
+++ b/packages/secretnote/src/modules/editor/model.ts
@@ -231,5 +231,5 @@ export class SecretNoteModel extends LibroModel {
 
   autoSave = debounce(() => {
     this.commandRegistry.executeCommand(DocumentCommands.Save.id);
-  }, 1000);
+  }, 500);
 }
diff --git a/packages/secretnote/src/modules/file/log-preview-contrib.tsx b/packages/secretnote/src/modules/file/log-preview-contrib.tsx
index 6969685c..7bc8e596 100644
--- a/packages/secretnote/src/modules/file/log-preview-contrib.tsx
+++ b/packages/secretnote/src/modules/file/log-preview-contrib.tsx
@@ -9,6 +9,6 @@ const LogViewer = React.lazy(() => import('@/components/log-viewer'));
 export class LogPreview implements FilePreviewContribution {
   type = 'log';
   render = (data: string) => {
-    return <LogViewer code={data} />;
+    return <LogViewer code={[data]} />;
   };
 }
diff --git a/packages/secretnote/src/modules/metrics/view.tsx b/packages/secretnote/src/modules/metrics/view.tsx
index ebfd8297..410f3bc0 100644
--- a/packages/secretnote/src/modules/metrics/view.tsx
+++ b/packages/secretnote/src/modules/metrics/view.tsx
@@ -2,7 +2,6 @@ import type { ModalItem, ModalItemProps } from '@difizen/mana-app';
 import { useInject } from '@difizen/mana-app';
 import { l10n } from '@difizen/mana-l10n';
 import { Badge, Drawer } from 'antd';
-import { forwardRef } from 'react';
 
 import { Smoothie } from '@/components/smoothie';
 
@@ -10,72 +9,6 @@ import { MetricsService } from './service';
 
 import './index.less';
 
-const MetricsComponent1 = forwardRef<HTMLDivElement, ModalItemProps<void>>(
-  function MetricsComponent1(props, ref) {
-    const { visible, close } = props;
-    const metricsService = useInject<MetricsService>(MetricsService);
-    const { metrics } = metricsService;
-
-    const afterOpenChange = async (open: boolean) => {
-      if (open) {
-        metricsService.enable();
-      }
-    };
-
-    const onClose = () => {
-      metricsService.disable();
-      close();
-    };
-
-    return (
-      <div ref={ref}>
-        <Drawer
-          placement="right"
-          onClose={() => onClose()}
-          width={360}
-          open={visible}
-          mask={false}
-          afterOpenChange={afterOpenChange}
-          destroyOnClose={true}
-          title={l10n.t('资源消耗')}
-        >
-          <div className="secretnote-kernel-status">
-            {metrics.map((item) => (
-              <div key={item.kernel.id} className="kernel-status-item">
-                <div className="server-name">{item.server.name}:</div>
-                <div className="metrics-item">
-                  <span className="label">{l10n.t('名称')}:</span>
-                  <span>{item.kernel.name}</span>
-                </div>
-                <div className="metrics-item">
-                  <span className="label">{l10n.t('状态')}:</span>
-                  <Badge
-                    color={item.kernel.statusColor}
-                    text={item.kernel.statusText}
-                  />
-                </div>
-                <div className="metrics-item">
-                  <span className="label">PID:</span>
-                  <span>{item.kernel.pid}</span>
-                </div>
-                <div className="metrics-item">
-                  <span className="label">CPU:</span>
-                  <span>{item.kernel.cpuText}</span>
-                </div>
-                <Smoothie data={{ time: Date.now(), data: item.kernel.cpu }} />
-                <div className="metrics-item">
-                  <span className="label">RAM:</span>
-                  <span>{item.kernel.memoryText}</span>
-                </div>
-                <Smoothie data={{ time: Date.now(), data: item.kernel.memory }} />
-              </div>
-            ))}
-          </div>
-        </Drawer>
-      </div>
-    );
-  },
-);
 const MetricsComponent = (props: ModalItemProps<void>) => {
   const { visible, close } = props;
   const metricsService = useInject<MetricsService>(MetricsService);
diff --git a/packages/secretnote/src/modules/scql-editor/editor/editor.ts b/packages/secretnote/src/modules/scql-editor/editor/editor.ts
index 54bb2b62..24bc8d88 100644
--- a/packages/secretnote/src/modules/scql-editor/editor/editor.ts
+++ b/packages/secretnote/src/modules/scql-editor/editor/editor.ts
@@ -9,7 +9,13 @@ import type {
   ICoordinate,
   SearchMatch,
 } from '@difizen/libro-jupyter';
-import { Disposable, DisposableCollection, watch, Emitter } from '@difizen/mana-app';
+import {
+  Disposable,
+  DisposableCollection,
+  watch,
+  Emitter,
+  Deferred,
+} from '@difizen/mana-app';
 import * as monaco from 'monaco-editor';
 import type { IMatching } from 'syntax-parser';
 
@@ -24,6 +30,8 @@ export type MonacoEditorType = monaco.editor.IStandaloneCodeEditor;
 export type MonacoMatch = monaco.editor.FindMatch;
 
 export class SQLEditor implements IEditor {
+  protected editorReadyDeferred = new Deferred<void>();
+  editorReady = this.editorReadyDeferred.promise;
   private editorHost: HTMLElement;
   private _model: IModel;
   private _uuid = '';
@@ -110,6 +118,7 @@ export class SQLEditor implements IEditor {
     );
 
     this.updateEditorSize();
+    this.editorReadyDeferred.resolve();
   }
 
   protected checkSyntaxError() {
diff --git a/pyprojects/secretnote/package.json b/pyprojects/secretnote/package.json
index d36cefaa..ec0085db 100644
--- a/pyprojects/secretnote/package.json
+++ b/pyprojects/secretnote/package.json
@@ -11,7 +11,7 @@
     "lint:ruff": "python -m ruff check src tests",
     "test:pytest": "python -m pytest",
     "typecheck:pyright": "pyright --project ../.. src tests",
-    "dev": "NODE_ENV=development python -m secretnote.server --config=./.jupyter/config_dev.py --debug --no-browser --mode=scql --party=alice --host=http://127.0.0.1:8991",
+    "dev": "NODE_ENV=development python -m secretnote.server --config=./.jupyter/config_dev.py --debug --no-browser",
     "build:py": "rye build --out ./dist",
     "publish:py": "exit 1",
     "build": "nx build:py"