DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

Session S3 — DeepSeek Harness 插件(DSH Plugin)
DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录
← Plugins
S

dsh-session-s3

Session S3

社区 DSH 插件:基于 S3 的 SessionPersistence(wal3-Lite)。不可变片段 + CAS 清单。

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add github:gengmao/dsh-session-s3#466331cb4519496536ce0889749729986b933a6e
README兼容性版本

兼容性与来源证明

Session S3 以 dsh-session-s3 发布,当前版本为 0.1.0。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
any
发布来源
github
Registry 更新时间
2026/8/20

版本

0.1.0stable
2026/8/20

相关插件

正在加载相关插件…

最新版
0.1.0
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
未提供
文件数
未提供
Surface
any
许可证
MIT
发布源
github
GitHub
★ 1
周下载
0
最近提交
2026/9/3
查看源码 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题

相关插件

继续浏览 memory-context 分类下经过校验的插件。

Contextdsh-context用于上下文洞察和管理的 DeepSeek Harness 插件,提供上下文仪表板和上下文命令,帮助了解上下文的构成及其演变过程。Weknora@wxg-prc-cpg/dsh-weknora适用于 DeepSeek Harness (dsh) 的 WeKnora 知识检索工具:通过自有知识库进行语义搜索、文档阅读以及 RAG/代理回答。Memsearch Dsh@zilliz/memsearch-dsh适用于 DeepSeek Harness 的 MemSearch 插件:在多个代理之间共享 Markdown 记忆,支持捕获、步骤前上下文注入、记忆召回技能和技能候选审核面板。Memory@furongjun1999/dsh-memory灵枢(Lingshu·líng shū)DeepSeek Harness 插件:完整大脑——长期记忆/知识飞轮/自我认知/递归反思接入 DSH,对话自动沉淀进 md_cg 认知图(md 文档)

README

dsh-session-s3

Unofficial community DeepSeek Harness plugin: an S3-backed SessionPersistence provider.

wal3-Lite: immutable JSONL fragments + a CAS manifest (If-None-Match / If-Match). Per-fragment SHA-256. No setsum in Phase 1.

Fixes the class of JSONL durability bugs that come from torn writes, missing fsync, and concurrent writers — by never mutating a fragment and coordinating writers on a single compare-and-swap object.

The 7 field-confirmed DSH corruption discussions (#1333, #1452, #1497, #1473, #1586, #2167, #2342) are encoded as tests in test/corruption-scenarios.test.ts. Results: docs/corruption-scenarios.md.

For the correctness boundary, commit point, crash states, sequence domains, and rejected alternatives, see Design rationale.

Install

Plugins install into a profile ($DSH_HOME/profiles/<name>), not globally. dsh plugin is a pnpm forwarder: it adds the package as a dependency, and because this repo declares dsh.bundle.patch, it also appends dsh-session-s3 to dsh.profile.bundles.

From GitHub (typical):

dsh plugin --profile web add github:gengmao/dsh-session-s3

or without a global dsh:

npx -y @deepseek-ai/dsh plugin --profile web add github:gengmao/dsh-session-s3

pnpm ≥10 blocks git-hosted prepare scripts (this package runs tsc on install). If add fails, allow the build and re-run:

# $DSH_HOME/profiles/web/pnpm-workspace.yaml
allowBuilds:
  dsh-session-s3: true

From a local checkout:

dsh plugin --profile web add ./dsh-session-s3
# or
dsh plugin --profile web add "link:$PWD"

From npm (once published):

dsh plugin --profile web add dsh-session-s3

Same action in the Web UI: Settings → Plugins. Restart dsh web (or dsh --profile web) after adding.

Remove with dsh plugin --profile web remove dsh-session-s3.

Requires Node >= 22 (PersistenceCoordinator uses Promise.withResolvers; a polyfill is loaded at import so 18–21 do not crash, but DSH itself wants 22).

Peer packages (@deepseek-ai/dsh-session, dsh-session-persistence) come from the DSH profile. A standalone npm install of this repo uses .npmrc legacy-peer-deps=true because @deepseek-ai/dsh-type-meta is unpublished on npmjs.

Config

KeyDefaultNotes
bucket(required)S3 bucket
prefixdsh/Keys live at {prefix}sessions/{sessionId}/
regionunset (SDK / AWS_REGION); auto if endpoint is setSet explicitly for AWS; auto for R2
endpoint—R2 / Tigris / MinIO / SeaweedFS / GCS interop (must be http(s))
forcePathStyletrue when endpoint is setPath-style URLs
accessKeyId / secretAccessKeyunsetOnly for static keys. Prefer the SDK default chain (see below)
flushThresholdEvents50Library createProvider() only: flush its in-memory buffer after N events
flushThresholdBytes262144Library createProvider() only: flush after 256 KiB
preparedSessionCacheSizecoordinator default (5)DSH seam: LRU of unpublished preparations
writeBatchMaxDelayMscoordinator default (200)DSH seam: coordinator write-behind delay

Invalid config fails loud at load, listing every problem (not just the first).

The bundle patch replaces the profile sessions row and the default export is a Cordis Service registered as ctx.sessionPersistence, so Harness resume/list/the agent loop talk to S3 instead of JSONL.

After install, set the bucket (and optional endpoint) in the profile overlay $DSH_HOME/profiles/web/cordis.patch.yml:

- id: sessions
  name: dsh-session-s3
  config:
    bucket: my-sessions
    region: us-west-2
    prefix: dsh/
    # endpoint: https://<account>.r2.cloudflarestorage.com   # R2 / MinIO / Tigris

Do not put access keys in the patch. See AWS credentials.

Library helper (no DSH, not the seam):

import { createProvider } from "dsh-session-s3";

const persistence = createProvider({
  bucket: "my-sessions",
  region: "us-west-2",
});

await persistence.append("sess-1", { type: "user/message", text: "hi" });
const events = await persistence.read("sess-1");
await persistence.compact("sess-1", 10); // keep the last 10 fragments
await persistence.close("sess-1");

AWS credentials

Resolution order (parseConfig → createS3Client):

  1. Plugin config accessKeyId and secretAccessKey (both required if either is set) — static keys, no session token
  2. Else omit keys and use the AWS SDK default chain (AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY + AWS_SESSION_TOKEN, ~/.aws/credentials, AWS_PROFILE, SSO, IAM instance/task role)

Prefer (2) on real AWS. parseConfig does not copy env keys into static credentials — that would drop AWS_SESSION_TOKEN and break SSO / assumed roles / GitHub OIDC. Leave accessKeyId / secretAccessKey unset in the Cordis patch.

Local / laptop

aws configure
# or
aws sso login --profile myprofile
export AWS_PROFILE=myprofile
export AWS_REGION=us-west-2
# then set bucket in the profile overlay, not via a DSH_S3_* env

IAM user / access keys (CI, MinIO, R2)

export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
# bucket still comes from the profile overlay `config.bucket`

Only set accessKeyId / secretAccessKey in plugin config when you cannot use env or the chain. Do not commit them.

EC2 / ECS / Lambda

Attach an instance or task role. Do not set keys. The SDK picks up the role.

IAM policy (minimum)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::my-sessions/dsh/*"
    },
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": "arn:aws:s3:::my-sessions",
      "Condition": {
        "StringLike": { "s3:prefix": ["dsh/*"] }
      }
    }
  ]
}

If-Match / If-None-Match are request headers, not extra IAM actions. list() / listSnapshots() call ListObjectsV2 and need s3:ListBucket.

R2 / MinIO / Tigris

Same keys, plus endpoint. Path-style is on automatically when endpoint is set:

export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
# set endpoint + bucket on the plugin config in the profile overlay

Check CAS

aws s3api put-object --bucket my-sessions --key dsh/_probe --body /dev/null \
  --if-none-match '*'

A retry should 412. If it always 200s, the store is ignoring preconditions and this plugin is unsafe there.

Object layout

s3://{bucket}/{prefix}sessions/{sessionId}/
├── manifest.json                 # CAS coordination point
└── fragments/00000001.jsonl      # immutable, SHA-256 in the manifest

Write path (flush):

  1. GET manifest.json. If the tail already has this batch's SHA-256, treat it as a lost CAS response.
  2. Buffer is serialized as a JSONL fragment (seq = last + 1). PutObject with If-None-Match: *. On 412, LIST occupied keys, take seq = max(manifest+1, maxOccupied+1), retry.
  3. Conditional PUT manifest.json with the ETag from step 1 (If-Match, quoted). Reload only after a 412. Idempotent on seq and tail sha256.
  4. Drop only the snapshotted prefix of the buffer after CAS succeeds (events appended during the flush stay).

Uncontended single-writer flush is three S3 requests. A crash between (2) and (3) leaves an orphan fragment. Harmless: the manifest is source of truth.

The successful conditional PUT of manifest.json is the commit point. Fragment sequence numbers order storage objects; they are not DSH event seq values. CAS preserves every writer's bytes but does not allocate cross-process event sequences. See Design rationale for the precise guarantees and remaining races.

S3 compatibility

BackendIf-Match / If-None-MatchPhase 1
AWS S3yes (quoted ETags)intended; live IT env-gated
Cloudflare R2yesintended
Tigrisyesintended
MinIOyesintended; S3_IT=1 against local MinIO
GCS (S3 interop)weak / eventual on some pathsuse with care
SeaweedFSversion-dependentuse with care

CAS is the correctness mechanism. Do not point this at a store that silently ignores conditional puts.

Env-gated integration test (and the CI MinIO integration job):

S3_IT=1 S3_BUCKET=test S3_ENDPOINT=http://127.0.0.1:9000 \
  S3_REGION=us-east-1 \
  AWS_ACCESS_KEY_ID=minioadmin AWS_SECRET_ACCESS_KEY=minioadmin \
  npm test

Caveats

  1. Same composition as JSONL. S3SessionPersistence extends @deepseek-ai/dsh-session-persistence's SessionPersistence and implements PersistenceBackend, then constructs PersistenceCoordinator(ctx, this). Cold load therefore emits synthetic interrupted-turn closers; instanceof SessionPersistence is true. Peer deps (cordis, dsh-session, dsh-session-persistence) are provided by the DSH profile at install time.
  2. One live writer per session. DSH does not support two processes concurrently writing the same SessionId. Manifest CAS is a defensive check: appendBatch revalidates SessionEvent.seq inside the CAS mutate and throws StaleWriterError instead of committing both batches. The loser's fragment PUT is an unreachable orphan. No leases, heartbeats, or fencing.
  3. No setsum (deliberate, Phase 2). Integrity is per-fragment SHA-256 only.
  4. Response ambiguity is bounded, not exactly-once. A fragment PUT can leave an orphan. A lost manifest response is recognized by matching the tail SHA-256 (library helper and DSH backend). Library fragments carry a nonce header so identical consecutive batches do not collapse.
  5. Library createProvider() is a 5-method helper (load/append/read/compact/close) for non-DSH callers. It does not interpret SessionEvent.seq. Concurrent library flushes reallocate a stale fragment ordinal above the committed tail instead of appending out of order. DSH uses the default class export. read() includes the in-memory buffer (not yet on S3). compact / close flush first. trim refuses a session that already has a DSH header.
  6. Trim vs concurrent readers. trim CAS-updates the manifest, then deletes dropped objects. A reader holding an old manifest that GETs a deleted fragment sees FragmentCorruptError. Phase 1 assumes one writer and no trim-during-read.
  7. Manifest rewrite is O(n) bytes per flush. Each commit rewrites the whole fragment list, so bytes transferred grow O(n²) over an untrimmed session. Fine for Phase 1; compact long-lived logs.
  8. Trim guard needs a parseable DSH header. trim refuses a session whose manifest survived schema parse. A DSH manifest whose header is malformed parses as , so out-of-band compact would not refuse it and could still brick . That intersection is not coded around.

Roadmap

  • Phase 2 — setsum / global log verification, verified GC
  • Optional — binary fragments, DynamoDB Streams notifications

Develop

npm install
npm test
npm run build

CI (.github/workflows/ci.yml) typechecks, runs the unit suite, then repeats the suite against MinIO with S3_IT=1.

SPEC.md is the Phase 1 contract.

header
pre-watermark
header: null
appendBatch