DeepSeek Harness: Plugin Configuration
Last updated: 2026-08-31
Configuration is a plugin's "control knob" — API keys, timeouts, feature toggles, these environment-specific parameters shouldn't be hardcoded but externalized through a configuration system. Schemastery is Cordis's declarative configuration framework: define once, automatically get type safety, UI forms, validation, and documentation.
📋 Prerequisites: Completed 15-define-tool.md, able to write basic tools
1. What You'll Learn
- Schemastery declarative configuration system
- Schema.string/number/boolean/object/array
- .required()/.default()/.description()
- Nested configuration with Schema.object
- Configuration display in the Web UI settings page
- Configuration validation and error messages
- Dynamic configuration updates
2. Schemastery Declarative Configuration
(1) Basic Concept
Schemastery is Cordis's built-in Schema definition library, inspired by Zod and JSON Schema, but designed specifically for interactive configuration.
import { Schema } from '@deepseek-ai/cordis'
export const Config = Schema.object({
apiKey: Schema.string().required().description('API key for the service'),
maxRetries: Schema.number().default(3).description('Maximum retry attempts'),
debug: Schema.boolean().default(false).description('Enable debug logging')
})
(2) Comparison with JSON Schema
| Dimension | Schemastery | JSON Schema |
|---|---|---|
| Syntax | Chainable API | JSON object |
| Type inference | ✅ Automatic | ❌ Manual |
| UI forms | ✅ Auto-generated | ❌ Extra tooling needed |
| Default values | .default() |
default field |
| Descriptions | .description() |
description field |
| Validation | Chainable validators | pattern/min/max etc. |
(3) ▶ Example 3
export const Config = Schema.object({
apiKey: Schema.string().required(),
maxRetries: Schema.number().default(3)
})
export const name = 'my-plugin'
export function apply(ctx: Context) {
// ctx.config type is automatically inferred
ctx.logger.info(`API key: ${ctx.config.apiKey}`)
ctx.logger.info(`Max retries: ${ctx.config.maxRetries}`)
}
The framework validates user-provided configuration values and injects them into ctx.config.
3. Schema Types
(1) ▶ Example 1
Schema.string() // Any string
Schema.string().required() // Required
Schema.string().default('hello') // Default value
Schema.string().description('Your name') // Description
Schema.string().pattern(/^[a-z]+$/) // Regex validation
Schema.string().min(1).max(100) // Length limits
(2) ▶ Example 2
Schema.number() // Any number
Schema.number().default(0) // Default value
Schema.number().min(0).max(100) // Range limits
Schema.number().step(1) // Step (for UI sliders)
Schema.integer() // Integer
(3) Schema.boolean
Schema.boolean() // Boolean
Schema.boolean().default(false) // Default false
(4) Schema.object
Schema.object({
host: Schema.string().default('localhost'),
port: Schema.number().default(5432),
ssl: Schema.boolean().default(false)
})
(5) Schema.array
Schema.array(Schema.string()) // String array
Schema.array(Schema.string()).default([]) // Default empty array
Schema.array(Schema.object({ // Object array
name: Schema.string().required(),
url: Schema.string().required()
}))
(6) Schema.union / Schema.const
// Enum values
Schema.union(['read', 'write', 'execute'])
// Constant
Schema.const('fixed-value')
// Enum with descriptions
Schema.union([
Schema.const('read').description('Read-only access'),
Schema.const('write').description('Read and write access'),
Schema.const('execute').description('Full access')
])
(7) Schema.dict
// Dictionary type
Schema.dict(
Schema.string(), // Value type
Schema.string() // Key type (optional)
)
4. Chainable Modifiers
(1) .required()
Marks as required. When the user doesn't provide it, the framework reports an error and blocks loading.
apiKey: Schema.string().required()
(2) .default(value)
Sets a default value. When the user doesn't provide it, the default is used without error.
maxRetries: Schema.number().default(3)
timeout: Schema.number().default(30000)
(3) .description(text)
Adds a description to a configuration item, displayed in the Web UI as hint text for form labels.
apiKey: Schema.string().required()
.description('API key obtained from the service dashboard')
(4) .min() / .max()
Numeric range or string length limits:
port: Schema.number().min(1).max(65535).default(8080)
name: Schema.string().min(1).max(50)
(5) .pattern()
Regex validation:
email: Schema.string().pattern(/^[^@]+@[^@]+\.[^@]+$/)
.description('Valid email address')
(6) .step()
Numeric step, affects UI slider precision:
timeout: Schema.number().min(1000).max(300000).step(1000).default(30000)
(7) Combined Usage
const Config = Schema.object({
apiKey: Schema.string()
.required()
.pattern(/^sk-[a-zA-Z0-9]+$/)
.description('DeepSeek API key (starts with sk-)'),
maxTokens: Schema.number()
.min(1).max(32768)
.default(4096)
.description('Maximum tokens per request'),
model: Schema.union(['deepseek-chat', 'deepseek-reasoner'])
.default('deepseek-chat')
.description('Model to use'),
temperature: Schema.number()
.min(0).max(2).step(0.1)
.default(0.7)
.description('Sampling temperature')
})
5. Nested Configuration
(1) Nested Objects
const Config = Schema.object({
database: Schema.object({
host: Schema.string().default('localhost'),
port: Schema.number().default(5432),
name: Schema.string().required(),
ssl: Schema.boolean().default(false)
}).default({ host: 'localhost', port: 5432, ssl: false }),
cache: Schema.object({
enabled: Schema.boolean().default(true),
ttl: Schema.number().default(3600).description('Cache TTL in seconds')
}).default({ enabled: true, ttl: 3600 })
})
(2) Deep Nesting
const Config = Schema.object({
llm: Schema.object({
provider: Schema.union(['deepseek', 'openai']).default('deepseek'),
deepseek: Schema.object({
apiKey: Schema.string().required(),
model: Schema.string().default('deepseek-chat')
}),
openai: Schema.object({
apiKey: Schema.string(),
endpoint: Schema.string().default('https://api.openai.com/v1')
})
})
})
(3) Reusable Schema
const ConnectionSchema = Schema.object({
host: Schema.string().default('localhost'),
port: Schema.number().default(5432),
timeout: Schema.number().default(5000)
})
const Config = Schema.object({
primary: ConnectionSchema.description('Primary connection'),
replica: ConnectionSchema.description('Replica connection')
})
6. Configuration Display in the Web UI Settings Page
(1) Auto Form Generation
Config defined with Schemastery automatically generates forms in the Web UI settings page:
| Schema Type | UI Control |
|---|---|
Schema.string() |
Text input |
Schema.number() |
Number input / slider |
Schema.boolean() |
Toggle switch |
Schema.union() |
Dropdown select |
Schema.array() |
List editor |
Schema.object() |
Grouped panel |
Schema.dict() |
Key-value editor |
(2) Description Display
Each field's .description() appears as hint text below the input:
┌─────────────────────────────────────────┐
│ API Key │
│ [sk-xxxxxxxxxxxxxxx ] │
│ DeepSeek API key (starts with sk-) │
├─────────────────────────────────────────┤
│ Max Tokens │
│ [4096 ] │
│ Maximum tokens per request │
└─────────────────────────────────────────┘
(3) Validation Feedback
When user input doesn't match the Schema, the UI shows immediate feedback:
┌─────────────────────────────────────────┐
│ API Key │
│ [invalid-key ] │
│ ❌ Must match pattern: /^sk-[a-zA-Z0-9]+$/ │
└─────────────────────────────────────────┘
7. Configuration Validation and Error Messages
(1) Startup Validation
When a plugin loads, the framework automatically validates its configuration:
const Config = Schema.object({
port: Schema.number().min(1).max(65535)
})
// User config: port: -1
// → Error: Invalid config for plugin my-plugin:
// port: must be >= 1
When validation fails, the plugin isn't loaded and the error is output to the terminal.
(2) Type Safety
TypeScript automatically infers ctx.config's type from the Config definition:
const Config = Schema.object({
apiKey: Schema.string().required(),
maxRetries: Schema.number().default(3)
})
export function apply(ctx: Context) {
ctx.config.apiKey // string ✅
ctx.config.maxRetries // number ✅
ctx.config.unknown // Type error ❌
}
(3) Custom Validation
const Config = Schema.object({
startDate: Schema.string().required(),
endDate: Schema.string().required()
}).validate((value) => {
if (new Date(value.startDate) > new Date(value.endDate)) {
throw new Error('startDate must be before endDate')
}
return value
})
8. Dynamic Configuration Updates
(1) Configuration Change Listening
export function apply(ctx: Context) {
ctx.on('config/updated', (newConfig) => {
ctx.logger.info('config updated:', newConfig)
// Adjust behavior based on new config
})
}
(2) Hot Update Flow
graph LR
UI[User modifies config] --> VALID[Schema validation]
VALID --> APPLY[Apply new config]
APPLY --> EMIT[Emit config/updated]
EMIT --> RELOAD[Plugin responds to update]
(3) Configs Requiring Restart
Some configuration changes (like port numbers or dependency injection) require a restart:
export function apply(ctx: Context) {
ctx.on('config/updated', (config) => {
if (config.port !== ctx.config.port) {
ctx.logger.warn('port change requires restart')
}
})
}
❓ FAQ
Config?export const Config. Other names won't be recognized.ctx.on('config/updated') to respond to configuration changes..hidden() modifier, which displays a password field in the UI: typescript apiKey: Schema.string().required().hidden() plugins.plugin-name.config, isolated from each other.📖 Summary
- Schemastery is Cordis's declarative configuration framework: define once, automatically get type safety, UI forms, and validation
- Supports Schema.string/number/boolean/object/array/union/dict
- Chainable modifiers: .required()/.default()/.description()/.min()/.max()/.pattern()
- Nested Schema.object organizes complex configurations; reusable Schema definitions
- Web UI auto-generates forms; description shows as hints; validation failures give immediate feedback
- Configuration changes are monitored via config/updated events; some changes require restart
📝 Exercises
1. ⭐ Basic: Add Config to the file_count tool with defaultPath (string, default current directory) and maxDepth (number, default 10). Verify form generation in the Web UI settings page.
2. ⭐⭐ Intermediate: Write a multi-connection configuration plugin. Config contains connections (array of objects, each with host/port/username/password), defined with nested Schema.object. Verify: errors when required fields are missing, default values fill correctly.
3. ⭐⭐⭐ Challenge: Create a configuration with custom validation: startDate and endDate must satisfy startDate < endDate; maxRetries must be a positive integer. Test: input invalid values and confirm validation error messages display correctly in the Web UI.