dsh-harbor-evolution 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/index.js +32 -6
- package/lib/candidate.js +58 -5
- package/lib/client.js +195 -54
- package/lib/dashboard.js +141 -20
- package/lib/evolution.js +139 -20
- package/lib/model-runtime.js +46 -5
- package/lib/runtime-identity.js +7 -0
- package/lib/service.js +187 -33
- package/lib/setup.js +2 -2
- package/lib/version.js +128 -0
- package/lib/web.js +5 -1
- package/package.json +8 -3
- package/skills/evolve-agent-with-harbor/SKILL.md +81 -7
- package/skills/evolve-agent-with-harbor/evals/evals.json +48 -3
- package/skills/evolve-agent-with-harbor/references/evaluator-upgrade.md +31 -0
- package/skills/evolve-agent-with-harbor/references/initialization.md +9 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Installable DeepSeek Harness Plugin + Skill for running stable Harbor evaluation and controlled Agent evolution loops, with a native DSH Web dashboard.
|
|
4
4
|
|
|
5
|
-
The package gives DSH
|
|
5
|
+
The package gives DSH fourteen strict Harbor tools, dedicated Tool cards, a nine-stage Evaluation Workbench, an installation Doctor, and the model- and user-invocable `evolve-agent-with-harbor` Skill. The Skill starts with four user-facing concepts—Dataset (what to test), Generator (who answers), Evaluator plus criteria (what good means), and Optimizer (who improves it)—then compiles accepted choices into the strict Evaluation Stack. A DSH Generator may explicitly pin the current default model as a non-secret Candidate identity while retaining the per-Job Host Broker credential boundary. The Plugin validates Dataset identity, checks Trial Lifecycle and Score Validity, governs independent Ground Truth meta-evaluation, diagnoses evidence provenance, limits each iteration to one controlled Candidate change, and invokes the Promotion Gate only as an explicit action.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -14,8 +14,8 @@ npx --yes dsh-harbor-evolution@latest setup --project-root "$PWD"
|
|
|
14
14
|
|
|
15
15
|
The setup command installs both required runtimes:
|
|
16
16
|
|
|
17
|
-
- `harbor-dsh-evolution==0.7.
|
|
18
|
-
- `dsh-harbor-evolution@0.7.
|
|
17
|
+
- `harbor-dsh-evolution==0.7.3` in a managed Python environment.
|
|
18
|
+
- `dsh-harbor-evolution@0.7.3` in the selected DSH profile.
|
|
19
19
|
|
|
20
20
|
It then stores the absolute Harbor executable paths and a fallback `projectRoot` in the profile's `harbor-evolution` block and verifies the integration. Agent Tool calls always use the calling session's absolute working directory as their project root; the configured value remains the Web Workbench and non-Agent fallback. Existing unrelated profile entries are preserved, and rerunning setup updates the same block.
|
|
21
21
|
|
|
@@ -37,6 +37,7 @@ Users may provide a single Query or Dataset path, a Generator curl or local Agen
|
|
|
37
37
|
The Plugin registers:
|
|
38
38
|
|
|
39
39
|
- `harbor_candidate_snapshot`
|
|
40
|
+
- `harbor_model_binding`
|
|
40
41
|
- `harbor_evolution_init`
|
|
41
42
|
- `harbor_evolution_doctor`
|
|
42
43
|
- `harbor_dataset_validate`
|
|
@@ -51,7 +52,7 @@ In the `web` profile, the same package also registers:
|
|
|
51
52
|
- descriptor-authorized Evaluator/Rubric source editing for `script` and `llm-as-judge` implementations, with optimistic concurrency and mandatory new identities;
|
|
52
53
|
- a `harbor-dsh-evaluator/v1` interface shared by deterministic scripts and LLM-as-Judge implementations;
|
|
53
54
|
- compact result cards for all Harbor Tool calls;
|
|
54
|
-
- a `Harbor Evolution` Settings section that checks the configured project, Evaluation Stack, Jobs directory, and CLI paths.
|
|
55
|
+
- a `Harbor Evolution` Settings section that checks the configured project, Evaluation Stack, Jobs directory, and CLI paths, supports process-local `projectRoot` reload, and checks npm for a newer formal release without silently installing it.
|
|
55
56
|
|
|
56
57
|
The Web UI is intentionally read-only. Starting an evaluation or deciding promotion remains an explicit Agent + Skill workflow, so a page refresh can never launch an expensive Job.
|
|
57
58
|
|
|
@@ -63,6 +64,10 @@ Before each Job, the Plugin snapshots the current DSH Agent selection—provider
|
|
|
63
64
|
|
|
64
65
|
`harbor_eval_run`, `harbor_context_preview`, and `harbor_evolution_doctor` inherit that selection by default. Advanced callers can override `candidateProvider` and `candidateModel` only as a pair, plus an optional `candidateReasoningEffort`. `openai-codex` performs a GPT Auth sign-in check before Harbor starts. The resulting model binding is part of Context v2 comparison identity, so any provider/model/reasoning change requires a new baseline.
|
|
65
66
|
|
|
67
|
+
`harbor_model_binding` returns the current default selection as a credential-free `model-binding.json` draft. Once included before Candidate snapshot, it enters the Candidate digest and becomes the required Job model identity. Conflicting Job or Plugin overrides fail before Harbor starts. Even for `openai-codex`, the Candidate receives only the short-lived Broker capability—never the Host OAuth file or an upstream API key.
|
|
68
|
+
|
|
69
|
+
When Settings opens, the Host performs a bounded npm registry check and caches successful results. An available release is shown with its exact installer command and release link. The browser never installs, rewrites a DSH profile, or restarts DSH; registry failures are non-blocking.
|
|
70
|
+
|
|
66
71
|
`harbor_eval_result` defaults to the stable Summary. Use `view=job`, `view=dataset`, `view=progress`, `view=trial` plus a returned `trialId`, or `view=governance` to inspect sanitized instructions, generated output, evidence, and evaluator source without coupling the Agent to artifact file paths.
|
|
67
72
|
|
|
68
73
|
## What setup writes
|
package/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
6
6
|
|
|
7
7
|
import { loadBundledSkill } from './lib/official-skill.js'
|
|
8
8
|
import { CandidateModelRuntime } from './lib/model-runtime.js'
|
|
9
|
+
import { RUNTIME_POLICY } from './lib/runtime-identity.js'
|
|
9
10
|
import { EvolutionService } from './lib/service.js'
|
|
10
11
|
import { installDashboardWeb } from './lib/web.js'
|
|
11
12
|
|
|
@@ -26,7 +27,6 @@ export const Config = Schema.object({
|
|
|
26
27
|
jobsDir: Schema.string().default('jobs'),
|
|
27
28
|
harborBin: Schema.string().default(''),
|
|
28
29
|
harborDshBin: Schema.string().default(''),
|
|
29
|
-
dshVersion: Schema.string().default('0.1.0-rc.6'),
|
|
30
30
|
agentImportPath: Schema.string().default('harbor_dsh_evolution.agent:DshCandidateAgent'),
|
|
31
31
|
pluginImportPath: Schema.string().default('dsh-evolution'),
|
|
32
32
|
pythonPath: Schema.string().default(''),
|
|
@@ -61,9 +61,16 @@ function toolProjectRoot(exec) {
|
|
|
61
61
|
return path.resolve(cwd)
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
export function synchronizeWorkbenchProjectRoot(service, exec) {
|
|
65
|
+
const projectRoot = toolProjectRoot(exec)
|
|
66
|
+
service.activateProjectRoot(projectRoot, 'agent-session')
|
|
67
|
+
return projectRoot
|
|
68
|
+
}
|
|
69
|
+
|
|
64
70
|
export function apply(ctx, config) {
|
|
65
71
|
const resolved = {
|
|
66
72
|
...config,
|
|
73
|
+
runtimePolicy: RUNTIME_POLICY,
|
|
67
74
|
projectRoot: path.resolve(config.projectRoot),
|
|
68
75
|
harborBin: config.harborBin || process.env.HARBOR_BIN || checkoutExecutable('harbor'),
|
|
69
76
|
harborDshBin: config.harborDshBin || process.env.HARBOR_DSH_BIN || checkoutExecutable('harbor-dsh'),
|
|
@@ -74,12 +81,12 @@ export function apply(ctx, config) {
|
|
|
74
81
|
),
|
|
75
82
|
}
|
|
76
83
|
const modelRuntime = new CandidateModelRuntime(ctx, resolved)
|
|
77
|
-
const metadata = { pluginVersion: packageJson.version }
|
|
84
|
+
const metadata = { pluginVersion: packageJson.version, projectRootSource: 'configured' }
|
|
78
85
|
const service = new EvolutionService(resolved, metadata, modelRuntime)
|
|
79
|
-
const serviceForTool = exec =>
|
|
80
|
-
|
|
81
|
-
projectRoot
|
|
82
|
-
}
|
|
86
|
+
const serviceForTool = exec => {
|
|
87
|
+
const projectRoot = synchronizeWorkbenchProjectRoot(service, exec)
|
|
88
|
+
return new EvolutionService({ ...resolved, projectRoot }, metadata, modelRuntime)
|
|
89
|
+
}
|
|
83
90
|
|
|
84
91
|
ctx.skills.register(loadBundledSkill())
|
|
85
92
|
installDashboardWeb(ctx, service)
|
|
@@ -94,11 +101,18 @@ export function apply(ctx, config) {
|
|
|
94
101
|
},
|
|
95
102
|
}, (args, exec) => serviceForTool(exec).snapshot(args)))
|
|
96
103
|
|
|
104
|
+
ctx.tools.register(jsonTool({
|
|
105
|
+
name: 'harbor_model_binding',
|
|
106
|
+
description: 'Freeze the current DSH default provider, model, and reasoning identity into a non-secret model-binding.json draft. Runtime access still uses the short-lived Host Model Broker capability.',
|
|
107
|
+
parameters: {},
|
|
108
|
+
}, (_args, exec) => serviceForTool(exec).modelBinding()))
|
|
109
|
+
|
|
97
110
|
ctx.tools.register(jsonTool({
|
|
98
111
|
name: 'harbor_evolution_init',
|
|
99
112
|
description: 'Compile an accepted Dataset, Generator, Evaluator/criteria, and Optimizer onboarding card into a strict, non-overwriting Evaluation Stack project. Detailed identity fields are internal tool inputs, not a user questionnaire.',
|
|
100
113
|
parameters: {
|
|
101
114
|
datasetPath: { type: 'string', required: true },
|
|
115
|
+
workspaceSubdir: { type: 'string', description: 'Optional namespace under the current project root. Defaults to the project root; use it to host multiple independent Harbor projects.' },
|
|
102
116
|
stackId: { type: 'string', required: true },
|
|
103
117
|
stackVersion: { type: 'string', required: true },
|
|
104
118
|
datasetId: { type: 'string', required: true },
|
|
@@ -131,6 +145,16 @@ export function apply(ctx, config) {
|
|
|
131
145
|
},
|
|
132
146
|
}, (args, exec) => serviceForTool(exec).doctor(args)))
|
|
133
147
|
|
|
148
|
+
ctx.tools.register(jsonTool({
|
|
149
|
+
name: 'harbor_quick_diagnostic_init',
|
|
150
|
+
description: 'Create a non-overwriting Harbor 1.4 wiring diagnostic with one Query, a minimal Host-model Candidate, a runnable Task, and an explicit non-promotion Evaluator. The supplied Rubric is recorded as a draft but is not treated as executed.',
|
|
151
|
+
parameters: {
|
|
152
|
+
query: { type: 'string', required: true },
|
|
153
|
+
rubric: { type: 'string', required: true },
|
|
154
|
+
workspaceSubdir: { type: 'string', description: 'Defaults to harbor-diagnostic under the current Agent session directory.' },
|
|
155
|
+
},
|
|
156
|
+
}, (args, exec) => serviceForTool(exec).quickDiagnostic(args)))
|
|
157
|
+
|
|
134
158
|
ctx.tools.register(jsonTool({
|
|
135
159
|
name: 'harbor_dataset_validate',
|
|
136
160
|
description: 'Validate dataset-manifest.json, task uniqueness, instructions, paths, sensitive metadata, and the immutable source digest.',
|
|
@@ -211,6 +235,7 @@ export function apply(ctx, config) {
|
|
|
211
235
|
description: 'Create a non-overwriting Ground Truth draft for evaluator meta-evaluation. GT may be human, programmatic, consensus, model, or external, but must have explicit provenance and remain independent of the Candidate evaluator.',
|
|
212
236
|
parameters: {
|
|
213
237
|
outputPath: { type: 'string', description: 'Defaults to .harbor/ground-truth.json' },
|
|
238
|
+
evaluationRoot: { type: 'string', description: 'Optional evaluation workspace root used to register custom Ground Truth paths.' },
|
|
214
239
|
groundTruthId: { type: 'string', required: true },
|
|
215
240
|
version: { type: 'string', required: true },
|
|
216
241
|
sourceKind: { type: 'string', required: true, description: 'human, programmatic, consensus, model, or external' },
|
|
@@ -227,6 +252,7 @@ export function apply(ctx, config) {
|
|
|
227
252
|
groundTruthPath: { type: 'string', description: 'Defaults to .harbor/ground-truth.json' },
|
|
228
253
|
observationsPath: { type: 'string', required: true },
|
|
229
254
|
outputPath: { type: 'string', description: 'Defaults to .harbor/meta-evaluation-report.json' },
|
|
255
|
+
evaluationRoot: { type: 'string', description: 'Optional evaluation workspace root used to register custom report paths.' },
|
|
230
256
|
},
|
|
231
257
|
}, (args, exec) => serviceForTool(exec).evaluatorMetaEvaluate(args)))
|
|
232
258
|
|
package/lib/candidate.js
CHANGED
|
@@ -2,7 +2,10 @@ import { createHash } from 'node:crypto'
|
|
|
2
2
|
import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
|
|
5
|
+
import { CANDIDATE_ACP_PACKAGE, DSH_RUNTIME_VERSION, RUNTIME_POLICY } from './runtime-identity.js'
|
|
6
|
+
|
|
5
7
|
export const MANIFEST_NAME = 'candidate-manifest.json'
|
|
8
|
+
export const MODEL_BINDING_NAME = 'model-binding.json'
|
|
6
9
|
const DIGEST_PREFIX = Buffer.from('harbor-dsh-candidate-v1\0')
|
|
7
10
|
const EXCLUDED_DIRS = new Set(['.git', 'node_modules', '__pycache__', '.harbor-runtime'])
|
|
8
11
|
const EXCLUDED_FILES = new Set([MANIFEST_NAME, '.DS_Store'])
|
|
@@ -16,6 +19,7 @@ const CREDENTIAL_FILES = new Set([
|
|
|
16
19
|
'id_rsa',
|
|
17
20
|
'id_ed25519',
|
|
18
21
|
])
|
|
22
|
+
const MODEL_BINDING_KEYS = new Set(['schema_version', 'source', 'provider', 'model', 'reasoning_effort'])
|
|
19
23
|
|
|
20
24
|
async function walk(root, current = root) {
|
|
21
25
|
const entries = await readdir(current, { withFileTypes: true })
|
|
@@ -33,6 +37,45 @@ async function walk(root, current = root) {
|
|
|
33
37
|
return files.sort((a, b) => Buffer.compare(Buffer.from(a.relative), Buffer.from(b.relative)))
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
export async function loadModelBinding(candidateDir) {
|
|
41
|
+
const pathname = path.join(path.resolve(candidateDir), MODEL_BINDING_NAME)
|
|
42
|
+
let value
|
|
43
|
+
try {
|
|
44
|
+
value = JSON.parse(await readFile(pathname, 'utf8'))
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error.code === 'ENOENT') return undefined
|
|
47
|
+
if (error instanceof SyntaxError) throw new Error(`${MODEL_BINDING_NAME} is not valid JSON`)
|
|
48
|
+
throw error
|
|
49
|
+
}
|
|
50
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
51
|
+
throw new Error(`${MODEL_BINDING_NAME} must be an object`)
|
|
52
|
+
}
|
|
53
|
+
const unknown = Object.keys(value).filter(key => !MODEL_BINDING_KEYS.has(key)).sort()
|
|
54
|
+
if (unknown.length) {
|
|
55
|
+
throw new Error(`${MODEL_BINDING_NAME} contains unsupported or secret-bearing fields: ${unknown.join(', ')}`)
|
|
56
|
+
}
|
|
57
|
+
if (value.schema_version !== 1) throw new Error(`${MODEL_BINDING_NAME} requires schema_version=1`)
|
|
58
|
+
const source = typeof value.source === 'string' ? value.source.trim() : ''
|
|
59
|
+
const provider = typeof value.provider === 'string' ? value.provider.trim() : ''
|
|
60
|
+
const model = typeof value.model === 'string' ? value.model.trim() : ''
|
|
61
|
+
if (!source || !provider || !model) {
|
|
62
|
+
throw new Error(`${MODEL_BINDING_NAME} requires non-empty source, provider, and model`)
|
|
63
|
+
}
|
|
64
|
+
const reasoningEffort = value.reasoning_effort === undefined
|
|
65
|
+
? undefined
|
|
66
|
+
: typeof value.reasoning_effort === 'string' ? value.reasoning_effort.trim() : ''
|
|
67
|
+
if (value.reasoning_effort !== undefined && !reasoningEffort) {
|
|
68
|
+
throw new Error(`${MODEL_BINDING_NAME} reasoning_effort must be a non-empty string when present`)
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
schema_version: 1,
|
|
72
|
+
source,
|
|
73
|
+
provider,
|
|
74
|
+
model,
|
|
75
|
+
...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
36
79
|
export async function computeCandidate(candidateDir) {
|
|
37
80
|
const root = path.resolve(candidateDir)
|
|
38
81
|
if (!(await stat(root)).isDirectory()) throw new Error(`Candidate path is not a directory: ${root}`)
|
|
@@ -83,6 +126,7 @@ async function validateCandidateContract(root) {
|
|
|
83
126
|
if (credentialPaths.length > 0) {
|
|
84
127
|
throw new Error(`Candidate contains credential-bearing files: ${credentialPaths.join(', ')}; inject credentials at runtime instead`)
|
|
85
128
|
}
|
|
129
|
+
await loadModelBinding(root)
|
|
86
130
|
}
|
|
87
131
|
|
|
88
132
|
export async function snapshotCandidate(candidateDir, options = {}) {
|
|
@@ -96,11 +140,18 @@ export async function snapshotCandidate(candidateDir, options = {}) {
|
|
|
96
140
|
}
|
|
97
141
|
const candidateId = options.candidateId ?? packageJson.name
|
|
98
142
|
const version = options.version ?? packageJson.version
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
throw new Error('Candidate id, version, and runtime version must not be empty; set package.json name/version or pass explicit values')
|
|
143
|
+
if (!candidateId || !version) {
|
|
144
|
+
throw new Error('Candidate id and version must not be empty; set package.json name/version or pass explicit values')
|
|
102
145
|
}
|
|
103
146
|
const computed = await computeCandidate(root)
|
|
147
|
+
const metadata = { ...(options.metadata ?? {}) }
|
|
148
|
+
const modelBinding = await loadModelBinding(root)
|
|
149
|
+
if (modelBinding) {
|
|
150
|
+
if (metadata.model_binding !== undefined && JSON.stringify(metadata.model_binding) !== JSON.stringify(modelBinding)) {
|
|
151
|
+
throw new Error('Candidate metadata model_binding must match model-binding.json')
|
|
152
|
+
}
|
|
153
|
+
metadata.model_binding = modelBinding
|
|
154
|
+
}
|
|
104
155
|
const manifest = {
|
|
105
156
|
schema_version: 1,
|
|
106
157
|
candidate_id: String(candidateId),
|
|
@@ -109,11 +160,13 @@ export async function snapshotCandidate(candidateDir, options = {}) {
|
|
|
109
160
|
created_at: new Date().toISOString(),
|
|
110
161
|
runtime: {
|
|
111
162
|
kind: 'deepseek-harness',
|
|
112
|
-
|
|
163
|
+
policy: RUNTIME_POLICY,
|
|
164
|
+
version: DSH_RUNTIME_VERSION,
|
|
165
|
+
package: CANDIDATE_ACP_PACKAGE,
|
|
113
166
|
transport: 'acp',
|
|
114
167
|
},
|
|
115
168
|
files: computed.files,
|
|
116
|
-
metadata
|
|
169
|
+
metadata,
|
|
117
170
|
}
|
|
118
171
|
await mkdir(root, { recursive: true })
|
|
119
172
|
await writeFile(path.join(root, MANIFEST_NAME), `${JSON.stringify(manifest, null, 2)}\n`)
|