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.

View source

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.

LayerScopeProgress
InfrastructureKafka transport and typed events, MySQL persistence, Redis cache invalidation, Elasticsearch search projections, and CDC pipelinesFoundation complete
Identity and authenticationUser directory, credential storage, token lifecycle, and authentication runtimeIn progress
AuthorizationUnified authentication and permission validation across user-facing operationsPlanned
Management experienceA graphical interface for users, access control, and system operationsPlanned

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:

sh
npx @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:

sh
git 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

LayerPackagesResponsibility
Authentication contractdsh-authSelects exactly one Provider by evidence kind, authenticates untrusted evidence, mints process-local AuthenticatedCall values, and checks their current provenance.
User directorydsh-user, dsh-user-mysqlDefines stable users, profile and lifecycle revisions, then persists that directory in MySQL.
Login credentialsdsh-user-credential, dsh-user-credential-mysqlDefines normalized identifiers and password state; the MySQL Provider owns uniqueness, scrypt verifiers, transactions, and dummy verification.
Refresh statedsh-auth-token, dsh-auth-token-mysqlDefines opaque refresh families, digest-only persistence, atomic rotation, replay detection, inspection, and revocation.
Account orchestrationdsh-account, dsh-account-mysqlCoordinates registration, login, refresh, logout, profile/password changes, and durable idempotent registration progress.
Authentication Providersdsh-auth-password, dsh-auth-jwtVerifies password evidence and independently signs and validates short-lived Access JWTs and rotating Refresh JWTs.
Entry and compositiondsh-auth-gateway, dsh-auth-starterEnforces 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

  1. Register: the adapter calls ctx.authGateway.register(), ctx.accounts records durable progress, creates the user, adds the normalized identifier, sets the password, and returns the completed UserRecord.
  2. Log in: the gateway passes the identifier and password to ctx.accounts; dsh-auth-password resolves the identifier and performs real or dummy verification, active-user state is checked, and dsh-auth-jwt creates an Access JWT, Refresh JWT, and server-side refresh family.
  3. 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-local AuthenticatedCall, and guard() checks it again immediately before protected work.
  4. 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.
  5. 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:

sh
pnpm 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:

yaml
1- 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:

sh
node -e "console.log(require('node:crypto').randomBytes(32).toString('base64url'))"

Start the Web profile with the patch:

sh
pnpm 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:

ts
1import 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.
  • AuthenticatedCall is Host-only, process-local authority. It must not cross JSON, RPC, session storage, or another process.
  • dsh-auth-gateway returns 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, and binlog_row_metadata=FULL. The CDC account needs REPLICATION CLIENT, REPLICATION SLAVE, and SELECT on 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-v1 and the permanently retained ordering-state index dsh-cdc-state-v1.
  • Redis needs no pre-created keys, but each deployment should have its own database or key prefix.

Check MySQL first:

sql
SHOW 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:

sh
pnpm 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:

yaml
1- 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:

yaml
password: !!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

sh
pnpm 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;
  • checkpointFile advances 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-plugin topic 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 assistantGroup surveyWeChat official account
DeepSeek Harness WeCom assistant QR codeDeepSeek Harness group survey QR codeDeepSeek Harness WeChat official account QR code

Contributing

See CONTRIBUTING.md.

Development

Start with the development guide and architecture documentation.

For agents, follow AGENTS.md.

License

MIT

Third-party dependencies and their licenses are disclosed in THIRD_PARTY_NOTICES.md.