deepseek-harness-multi-userDeepSeek Harness plugin
Building a multi-user DeepSeek Harness with Kafka, MySQL, Redis, Elasticsearch and CDC infrastructure, authentication, authorization, and a web management UI.
- Stars
- 78
- Forks
- 11
- License
- MIT
- Last commit
- Aug 26, 2026
Overview
Building a multi-user DeepSeek Harness with Kafka, MySQL, Redis, Elasticsearch and CDC infrastructure, authentication, authorization, and a web management UI.
Original README
Cached from the project repository on Sep 3, 2026. This is source content, separate from the Agents.md review above.
DeepSeek Harness Multi-User
English | 中文
DeepSeek Harness (dsh) is an open-source agent harness developed by DeepSeek AI.
It uses an architecture where everything is a plugin, and is powered by Cordis, whose design is described in A Programming Paradigm for Spatiotemporal Composability.
Multi-user edition
This fork is evolving DeepSeek Harness from a single-user agent runtime into a multi-user platform. It preserves the plugin architecture while adding shared infrastructure, user identity, authentication, authorization, and an operational Web UI.
| Layer | Scope | Progress |
|---|---|---|
| Infrastructure | Kafka transport and typed events, MySQL persistence, Redis cache invalidation, Elasticsearch search projections, and CDC pipelines | Foundation complete |
| Identity and authentication | User directory, credential storage, token lifecycle, and authentication runtime | In progress |
| Authorization | Unified authentication and permission validation across user-facing operations | Planned |
| Management experience | A graphical interface for users, access control, and system operations | Planned |
The design separates durable domain state from projections and cache state: MySQL owns persistence, Kafka carries change events, CDC coordinates propagation, Redis handles invalidation, and Elasticsearch serves search. This foundation lets authentication and authorization evolve without coupling user-facing workflows to one storage engine.
Developer preview
DeepSeek Harness is currently in developer preview and is iterating rapidly. THERE WILL BE COMPATIBILITY-BREAKING CHANGES.
Run
Run from npm
Install Node.js, then run:
shnpx @deepseek-ai/dsh web
The command starts the Web UI, served at http://127.0.0.1:3080 by default. See Web UI guide.
Run from source
To run from a repository checkout:
shgit clone https://github.com/deepseek-ai/deepseek-harness.git cd deepseek-harness pnpm install pnpm run build pnpm dsh web
Authentication suite
The authentication suite provides a complete Host-only account path without coupling authentication to HTTP routing or authorization policy. The default dsh-auth-starter entry assembles MySQL-backed users, login credentials, refresh-token families, durable registration, Password and JWT authentication, account operations, and a transport-neutral gateway. Applications normally mount the Starter instead of wiring every package independently.
Packages and responsibilities
| Layer | Packages | Responsibility |
|---|---|---|
| Authentication contract | dsh-auth | Selects exactly one Provider by evidence kind, authenticates untrusted evidence, mints process-local AuthenticatedCall values, and checks their current provenance. |
| User directory | dsh-user, dsh-user-mysql | Defines stable users, profile and lifecycle revisions, then persists that directory in MySQL. |
| Login credentials | dsh-user-credential, dsh-user-credential-mysql | Defines normalized identifiers and password state; the MySQL Provider owns uniqueness, scrypt verifiers, transactions, and dummy verification. |
| Refresh state | dsh-auth-token, dsh-auth-token-mysql | Defines opaque refresh families, digest-only persistence, atomic rotation, replay detection, inspection, and revocation. |
| Account orchestration | dsh-account, dsh-account-mysql | Coordinates registration, login, refresh, logout, profile/password changes, and durable idempotent registration progress. |
| Authentication Providers | dsh-auth-password, dsh-auth-jwt | Verifies password evidence and independently signs and validates short-lived Access JWTs and rotating Refresh JWTs. |
| Entry and composition | dsh-auth-gateway, dsh-auth-starter | Enforces HTTP/WebSocket credential carriers and composes the complete MySQL suite in dependency order. |
dsh-user, dsh-user-credential, and dsh-auth-token are Provider-neutral service definitions. Their MySQL packages own durable data; Password and JWT packages consume those services without owning their tables.
Request lifecycle
- Register: the adapter calls
ctx.authGateway.register(),ctx.accountsrecords durable progress, creates the user, adds the normalized identifier, sets the password, and returns the completedUserRecord. - Log in: the gateway passes the identifier and password to
ctx.accounts;dsh-auth-passwordresolves the identifier and performs real or dummy verification, active-user state is checked, anddsh-auth-jwtcreates an Access JWT, Refresh JWT, and server-side refresh family. - Protect a request: an Authorization Bearer reaches
authenticateHttp(); the JWT Provider verifies signature, issuer, audience, Token type, expiry, family state, and active-user state. The gateway returns a process-localAuthenticatedCall, andguard()checks it again immediately before protected work. - Refresh: the gateway requires the configured Refresh Cookie, an exact allowed Origin, and matching CSRF header and readable Cookie. It verifies the Refresh JWT and atomically rotates the opaque server-side Credential; the returned directive sets the replacement Refresh Cookie as Secure and HttpOnly, and replaying a rotated Refresh JWT revokes the whole family.
- Log out: the gateway authenticates the Access Bearer, revalidates the call, revokes every JWT family for that user, and returns directives that clear the Refresh and CSRF Cookies.
Run the complete MySQL suite from source
After the source installation above, link the Starter into the Web profile:
shpnpm dsh plugin --profile web add ./packages/identity/auth-starter
Create auth.cordis.yml in the repository root. The values and field names below are the Starter's published Cordis configuration:
yaml1- insert: 2 - id: authentication 3 name: '@deepseek-ai/dsh-auth-starter' 4 config: 5 mysql: 6 host: 127.0.0.1 7 user: dsh 8 password: !!js env.DSH_MYSQL_PASSWORD 9 database: dsh 10 jwt: 11 issuer: https://auth.example 12 audience: dsh-web 13 activeKeyId: primary 14 keys: 15 - keyId: primary 16 secret: !!js env.DSH_AUTH_JWT_SECRET 17 gateway: 18 allowedOrigins: [https://app.example]
The Starter mounts only the Host-side ctx.authGateway service. It does not add HTTP routes, framework middleware, or a Web login UI; a framework adapter must translate native requests and responses to and from the gateway API before users can register or log in.
Generate a 32-byte key with Node.js on Windows, macOS, or Linux, then set the printed value as the DSH_AUTH_JWT_SECRET environment variable for the Harness process. Do not paste the value into this file or commit it:
shnode -e "console.log(require('node:crypto').randomBytes(32).toString('base64url'))"
Start the Web profile with the patch:
shpnpm dsh web --patch ./auth.cordis.yml
DSH_AUTH_JWT_SECRET must be the canonical Base64url encoding of 32-128 random bytes. MySQL credentials and JWT signing material are mandatory; the Starter has no production secret defaults. The MySQL account must be allowed to create and use the suite-owned tables.
Call the public gateway
Framework adapters pass structured headers, parsed Cookies, decoded Query entries, and bounded body fields to ctx.authGateway. This example uses the same public registration, login, Access Bearer, and guard() APIs exercised by the Starter tests:
ts1import type { Context } from '@deepseek-ai/cordis' 2import '@deepseek-ai/dsh-auth-gateway' 3 4const signal = new AbortController().signal 5 6export async function registerAndAuthenticate(ctx: Context): Promise<string> { 7 const user = await ctx.authGateway.register({ 8 requestId: 'register-1', 9 signal, 10 identifier: { kind: 'username', value: 'alice' }, 11 password: 'correct horse battery staple', 12 displayName: 'Alice', 13 }) 14 15 const login = await ctx.authGateway.login({ 16 requestId: 'login-1', 17 signal, 18 identifier: { kind: 'username', value: 'alice' }, 19 password: 'correct horse battery staple', 20 }) 21 22 const call = await ctx.authGateway.authenticateHttp({ 23 requestId: 'request-1', 24 signal, 25 headers: [{ name: 'Authorization', value: `Bearer ${login.accessToken}` }], 26 }) 27 28 return ctx.authGateway.guard(call, current => current.principal.id === user.userId 29 ? current.principal.id 30 : Promise.reject(new Error('authenticated user changed'))) 31}
Login returns adapter-neutral Cookie directives: the Refresh value defaults to the Secure, HttpOnly, SameSite=Strict __Host-dsh_refresh Cookie, while __Host-dsh_csrf carries the readable double-submit value. Adapters must use maintained Cookie parsers and must not log request objects, session results, tokens, passwords, or Cookie directives.
Security boundaries
- Access and Refresh JWTs are distinct signed artifacts and are accepted only by their own flows; Refresh also requires current server-side family state.
- Refresh rotation is single-use. Reuse of an old Refresh JWT revokes the family rather than issuing another session.
AuthenticatedCallis Host-only, process-local authority. It must not cross JSON, RPC, session storage, or another process.dsh-auth-gatewayreturns structured operations and Cookie directives; it is not an HTTP router or framework middleware.- The Starter mounts account administration but no administrator authorizer. Administration fails closed until one explicit authorization Provider is registered.
- Rate limiting, lockout, recovery, RBAC, tenant derivation, and long-lived WebSocket expiry policy remain separate plugins.
For the complete contracts, see the Starter guide, authentication runtime, user directory, user credentials, refresh-token lifecycle, and generated configuration catalog.
Quick MySQL, Kafka, Redis, and Elasticsearch setup
The following example publishes row changes made to app.users after startup to Kafka, then projects them independently into Redis and Elasticsearch. It uses the generic CDC plugins; for the session-specific composition, see dsh-session-cdc-starter.
1. Prepare external resources
- MySQL must use
log_bin=ON,binlog_format=ROW,binlog_row_image=FULL, andbinlog_row_metadata=FULL. The CDC account needsREPLICATION CLIENT,REPLICATION SLAVE, andSELECTon each routed table. - Pre-create the Kafka topic
dsh.cdc.users; this project does not auto-create topics. - Pre-create the Elasticsearch target index
dsh-users-v1and the permanently retained ordering-state indexdsh-cdc-state-v1. - Redis needs no pre-created keys, but each deployment should have its own database or key prefix.
Check MySQL first:
sqlSHOW VARIABLES WHERE Variable_name IN ('log_bin', 'binlog_format', 'binlog_row_image', 'binlog_row_metadata'); SHOW GRANTS FOR 'dsh_cdc'@'%';
2. Enter connection details
These plugins are not mounted in the Web profile by default. For a source checkout, link them into that profile from the repository root after pnpm install and pnpm run build:
shpnpm dsh plugin --profile web add ./packages/multi/kafka ./packages/multi/redis ./packages/multi/elasticsearch ./packages/multi/cdc ./packages/multi/cdc-redis ./packages/multi/cdc-elasticsearch
Create cdc.cordis.yml in the repository root and replace the sample endpoints, credentials, database, table, and primary key:
yaml1- insert: 2 - id: kafka 3 name: '@deepseek-ai/dsh-kafka' 4 config: 5 binding: 'main-kafka' 6 brokers: ['kafka.example.com:9092'] 7 clientId: 'dsh-cdc' 8 tls: false 9 topics: ['dsh.cdc.users'] 10 consumerGroups: ['dsh-cdc-redis', 'dsh-cdc-elasticsearch'] 11 12 - id: redis 13 name: '@deepseek-ai/dsh-redis' 14 config: 15 url: 'redis://username:password@redis.example.com:6379/0' 16 17 - id: elasticsearch 18 name: '@deepseek-ai/dsh-elasticsearch' 19 config: 20 node: 'https://elasticsearch.example.com:9200' 21 auth: 22 username: 'elastic' 23 password: 'replace-me' 24 maxRetries: 3 25 requestTimeoutMs: 10000 26 pingTimeoutMs: 5000 27 28 - id: users-redis-projection 29 name: '@deepseek-ai/dsh-cdc-redis' 30 config: 31 subscriptionId: 'users-redis' 32 consumerGroup: 'dsh-cdc-redis' 33 topics: ['dsh.cdc.users'] 34 fallbackMode: 'latest' 35 routes: 36 - database: 'app' 37 table: 'users' 38 topic: 'dsh.cdc.users' 39 keyPrefix: 'dsh:users' 40 41 - id: users-search-projection 42 name: '@deepseek-ai/dsh-cdc-elasticsearch' 43 config: 44 subscriptionId: 'users-elasticsearch' 45 consumerGroup: 'dsh-cdc-elasticsearch' 46 topics: ['dsh.cdc.users'] 47 fallbackMode: 'latest' 48 stateIndex: 'dsh-cdc-state-v1' 49 routes: 50 - database: 'app' 51 table: 'users' 52 topic: 'dsh.cdc.users' 53 index: 'dsh-users-v1' 54 55 - id: mysql-cdc 56 name: '@deepseek-ai/dsh-cdc' 57 config: 58 host: 'mysql.example.com' 59 port: 3306 60 user: 'dsh_cdc' 61 password: 'replace-me' 62 serverId: 7102 63 checkpointFile: './data/cdc/mysql-main.json' 64 routes: 65 - database: 'app' 66 table: 'users' 67 topic: 'dsh.cdc.users' 68 primaryKey: ['id'] 69 excludeColumns: ['password_hash']
Do not commit production passwords to Git. Cordis JavaScript values can read them from environment variables instead:
yamlpassword: !!js process.env.DSH_MYSQL_CDC_PASSWORD
For Kafka TLS or SASL, fill in tls and sasl as described in the dsh-kafka configuration guide. A trusted local plaintext Elasticsearch node requires explicit allowInsecureHttp: true.
3. Start and verify
shpnpm dsh web --patch ./cdc.cordis.yml
After startup, insert or update one MySQL row and verify that:
- Kafka receives a CDC event on
dsh.cdc.users; - Redis contains a JSON key beginning with
dsh:users:; - Elasticsearch contains the corresponding document in
dsh-users-v1; checkpointFileadvances and is reused after a restart.
Redis and Elasticsearch must use different consumer groups. Sharing a group would split records between them instead of delivering every record to both. fallbackMode: latest processes only records published after a new consumer starts; use earliest for an intentional first-time replay. CDC does not copy historical MySQL rows, so existing data requires a separate initial import or reconciliation.
serverId must be unique among replication clients connected to the same MySQL server. Every excludeColumns entry must exist in the table; remove the sample password_hash entry if your table does not have that column. See the configuration catalog for every field constraint.
Community and support
-
Feel free to submit feedback or bug reports through GitHub Discussions.
-
Add the
dsh-plugintopic to your plugin repository for discoverability. -
Join the DeepSeek Harness WeCom group by scanning the assistant QR code and completing the survey; the assistant will invite you after submission.
| WeCom assistant | Group survey | WeChat official account |
|---|---|---|
![]() | ![]() | ![]() |
Contributing
See CONTRIBUTING.md.
Development
Start with the development guide and architecture documentation.
For agents, follow AGENTS.md.
License
Third-party dependencies and their licenses are disclosed in THIRD_PARTY_NOTICES.md.


