dsh-harbor-evolution 0.3.1 → 0.5.0
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 +18 -6
- package/index.js +69 -7
- package/lib/candidate.js +29 -2
- package/lib/client.js +321 -0
- package/lib/dashboard.js +266 -0
- package/lib/evolution.js +105 -26
- package/lib/service.js +67 -0
- package/lib/setup.js +7 -1
- package/lib/web.js +76 -0
- package/package.json +23 -4
- package/skills/evolve-agent-with-harbor/SKILL.md +105 -78
- package/skills/evolve-agent-with-harbor/references/initialization.md +89 -83
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# dsh-harbor-evolution
|
|
2
2
|
|
|
3
|
-
Installable DeepSeek Harness Plugin + Skill for running stable Harbor evaluation and controlled Agent evolution loops.
|
|
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 eight strict Harbor tools, dedicated Tool cards, a paginated Evaluation Workbench, an installation Doctor, and the model- and user-invocable `evolve-agent-with-harbor` Skill. The Skill clarifies and initializes the Evaluation Stack, validates Dataset identity, runs Architecture Doctor and Context v2 preview, establishes a baseline, diagnoses evidence, limits each iteration to one controlled Candidate change, and applies the Promotion Gate.
|
|
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.
|
|
18
|
-
- `dsh-harbor-evolution@0.
|
|
17
|
+
- `harbor-dsh-evolution==0.5.0` in a managed Python environment.
|
|
18
|
+
- `dsh-harbor-evolution@0.5.0` in the selected DSH profile.
|
|
19
19
|
|
|
20
20
|
It then stores the absolute Harbor executable paths and `projectRoot` in the profile's `harbor-evolution` block and verifies the integration. Existing unrelated profile entries are preserved, and rerunning setup updates the same block.
|
|
21
21
|
|
|
@@ -35,11 +35,23 @@ Inspect this workspace and help me clarify and initialize a stable Harbor self-e
|
|
|
35
35
|
The Plugin registers:
|
|
36
36
|
|
|
37
37
|
- `harbor_candidate_snapshot`
|
|
38
|
+
- `harbor_evolution_init`
|
|
39
|
+
- `harbor_evolution_doctor`
|
|
40
|
+
- `harbor_dataset_validate`
|
|
41
|
+
- `harbor_context_preview`
|
|
38
42
|
- `harbor_eval_run`
|
|
39
43
|
- `harbor_eval_result`
|
|
40
44
|
- `harbor_candidate_compare`
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
In the `web` profile, the same package also registers:
|
|
47
|
+
|
|
48
|
+
- a lightweight Harbor Job overview and on-demand Evaluation Workbench for Contract, Stack, Dataset, Doctor, paginated Trials, evidence, optimization, promotion, and audit artifacts;
|
|
49
|
+
- compact result cards for all Harbor Tool calls;
|
|
50
|
+
- a `Harbor Evolution` Settings section that checks the configured project, Evaluation Stack, Jobs directory, and CLI paths.
|
|
51
|
+
|
|
52
|
+
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.
|
|
53
|
+
|
|
54
|
+
A direct evaluation requires `candidatePath`, `datasetPath`, `stackPath`, and explicit `mode`; `promotion-eligible` additionally requires `policyPath`. Prefer the Skill because it will not run or compare Jobs until the material identities and evaluation contract are resolved.
|
|
43
55
|
|
|
44
56
|
## What setup writes
|
|
45
57
|
|
|
@@ -63,7 +75,7 @@ For source development from the repository:
|
|
|
63
75
|
./hse dsh-install-source web
|
|
64
76
|
```
|
|
65
77
|
|
|
66
|
-
Do not use `dsh plugin add ./packages/dsh-plugin` directly from a fresh checkout. pnpm records a `link:` dependency, and Node resolves imports from the real checkout path. The source installer first runs the package's locked `npm ci`, then links it and installs the local Python Adapter. Normal users should always use the registry-backed setup command above.
|
|
78
|
+
Do not use `dsh plugin add ./packages/dsh-plugin` directly from a fresh checkout. pnpm records a `link:` dependency, and Node resolves imports from the real checkout path. The source installer first runs the package's locked `npm ci`, builds the portable Web client with its embedded ocean artwork, then links it and installs the local Python Adapter. Normal users should always use the registry-backed setup command above.
|
|
67
79
|
|
|
68
80
|
See the [complete DSH Web quickstart](https://github.com/istarwyh/harbor-self-evolving/blob/main/docs/dsh-web-quickstart.md) for UI verification, first evaluation, Candidate comparison, and troubleshooting.
|
|
69
81
|
|
package/index.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import Schema from '@deepseek-ai/schemastery'
|
|
2
2
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
3
|
-
import { existsSync } from 'node:fs'
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
6
6
|
|
|
7
|
-
import { compareCandidates, readEvaluation, runEvaluation, snapshot } from './lib/evolution.js'
|
|
8
7
|
import { loadBundledSkill } from './lib/official-skill.js'
|
|
8
|
+
import { EvolutionService } from './lib/service.js'
|
|
9
|
+
import { installDashboardWeb } from './lib/web.js'
|
|
9
10
|
|
|
10
11
|
export const name = 'harbor-evolution'
|
|
11
12
|
export const inject = ['tools', 'skills']
|
|
12
13
|
|
|
13
14
|
const packageDir = path.dirname(fileURLToPath(import.meta.url))
|
|
14
15
|
const checkoutPythonPackage = path.resolve(packageDir, '../harbor-plugin')
|
|
16
|
+
const packageJson = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'))
|
|
15
17
|
|
|
16
18
|
function checkoutExecutable(name) {
|
|
17
19
|
const candidate = path.join(checkoutPythonPackage, '.venv', 'bin', name)
|
|
@@ -55,8 +57,10 @@ export function apply(ctx, config) {
|
|
|
55
57
|
: ''
|
|
56
58
|
),
|
|
57
59
|
}
|
|
60
|
+
const service = new EvolutionService(resolved, { pluginVersion: packageJson.version })
|
|
58
61
|
|
|
59
62
|
ctx.skills.register(loadBundledSkill())
|
|
63
|
+
installDashboardWeb(ctx, service)
|
|
60
64
|
|
|
61
65
|
ctx.tools.register(jsonTool({
|
|
62
66
|
name: 'harbor_candidate_snapshot',
|
|
@@ -66,19 +70,77 @@ export function apply(ctx, config) {
|
|
|
66
70
|
candidateId: { type: 'string' },
|
|
67
71
|
version: { type: 'string' },
|
|
68
72
|
},
|
|
69
|
-
}, args => snapshot(
|
|
73
|
+
}, args => service.snapshot(args)))
|
|
74
|
+
|
|
75
|
+
ctx.tools.register(jsonTool({
|
|
76
|
+
name: 'harbor_evolution_init',
|
|
77
|
+
description: 'Initialize a strict, non-overwriting Evaluation Stack project after the Skill has clarified identities, primary metric, judge, and promotion threshold.',
|
|
78
|
+
parameters: {
|
|
79
|
+
datasetPath: { type: 'string', required: true },
|
|
80
|
+
stackId: { type: 'string', required: true },
|
|
81
|
+
stackVersion: { type: 'string', required: true },
|
|
82
|
+
datasetId: { type: 'string', required: true },
|
|
83
|
+
datasetVersion: { type: 'string', required: true },
|
|
84
|
+
contractId: { type: 'string', required: true },
|
|
85
|
+
contractVersion: { type: 'string', required: true },
|
|
86
|
+
primaryMetric: { type: 'string', required: true },
|
|
87
|
+
primaryDirection: { type: 'string', required: true },
|
|
88
|
+
judgeProvider: { type: 'string', required: true },
|
|
89
|
+
judgeModel: { type: 'string', required: true },
|
|
90
|
+
judgeVersion: { type: 'string', required: true },
|
|
91
|
+
policyId: { type: 'string', required: true },
|
|
92
|
+
policyVersion: { type: 'string', required: true },
|
|
93
|
+
minImprovement: { type: 'number', required: true },
|
|
94
|
+
},
|
|
95
|
+
}, args => service.initialize(args)))
|
|
96
|
+
|
|
97
|
+
ctx.tools.register(jsonTool({
|
|
98
|
+
name: 'harbor_evolution_doctor',
|
|
99
|
+
description: 'Validate the Evaluation Stack architecture, Dataset manifest, Candidate, and optional Promotion Policy before an expensive Harbor Job.',
|
|
100
|
+
parameters: {
|
|
101
|
+
candidatePath: { type: 'string', required: true },
|
|
102
|
+
datasetPath: { type: 'string', required: true },
|
|
103
|
+
stackPath: { type: 'string', required: true },
|
|
104
|
+
policyPath: { type: 'string' },
|
|
105
|
+
mode: { type: 'string', required: true },
|
|
106
|
+
},
|
|
107
|
+
}, args => service.doctor(args)))
|
|
108
|
+
|
|
109
|
+
ctx.tools.register(jsonTool({
|
|
110
|
+
name: 'harbor_dataset_validate',
|
|
111
|
+
description: 'Validate dataset-manifest.json, task uniqueness, instructions, paths, sensitive metadata, and the immutable source digest.',
|
|
112
|
+
parameters: {
|
|
113
|
+
datasetPath: { type: 'string', required: true },
|
|
114
|
+
},
|
|
115
|
+
}, args => service.validateDataset(args)))
|
|
116
|
+
|
|
117
|
+
ctx.tools.register(jsonTool({
|
|
118
|
+
name: 'harbor_context_preview',
|
|
119
|
+
description: 'Preview Evaluation Context v2 and find comparable baselines before launching a Job.',
|
|
120
|
+
parameters: {
|
|
121
|
+
candidatePath: { type: 'string', required: true },
|
|
122
|
+
candidateId: { type: 'string' },
|
|
123
|
+
version: { type: 'string' },
|
|
124
|
+
datasetPath: { type: 'string', required: true },
|
|
125
|
+
stackPath: { type: 'string', required: true },
|
|
126
|
+
mode: { type: 'string', required: true },
|
|
127
|
+
},
|
|
128
|
+
}, args => service.previewContext(args)))
|
|
70
129
|
|
|
71
130
|
ctx.tools.register(jsonTool({
|
|
72
131
|
name: 'harbor_eval_run',
|
|
73
|
-
description: '
|
|
132
|
+
description: 'Run a strict diagnostic or promotion-eligible Harbor Job bound to Candidate, Dataset Manifest, Evaluation Stack, and Context v2 identities.',
|
|
74
133
|
parameters: {
|
|
75
134
|
candidatePath: { type: 'string', required: true },
|
|
76
135
|
candidateId: { type: 'string' },
|
|
77
136
|
version: { type: 'string' },
|
|
78
137
|
datasetPath: { type: 'string', required: true },
|
|
138
|
+
stackPath: { type: 'string', required: true },
|
|
139
|
+
mode: { type: 'string', required: true },
|
|
140
|
+
policyPath: { type: 'string' },
|
|
79
141
|
jobName: { type: 'string' },
|
|
80
142
|
},
|
|
81
|
-
}, args =>
|
|
143
|
+
}, args => service.run(args)))
|
|
82
144
|
|
|
83
145
|
ctx.tools.register(jsonTool({
|
|
84
146
|
name: 'harbor_eval_result',
|
|
@@ -86,7 +148,7 @@ export function apply(ctx, config) {
|
|
|
86
148
|
parameters: {
|
|
87
149
|
jobPath: { type: 'string', required: true },
|
|
88
150
|
},
|
|
89
|
-
}, args =>
|
|
151
|
+
}, args => service.result(args)))
|
|
90
152
|
|
|
91
153
|
ctx.tools.register(jsonTool({
|
|
92
154
|
name: 'harbor_candidate_compare',
|
|
@@ -96,5 +158,5 @@ export function apply(ctx, config) {
|
|
|
96
158
|
candidateJob: { type: 'string', required: true },
|
|
97
159
|
policyPath: { type: 'string', required: true },
|
|
98
160
|
},
|
|
99
|
-
}, args =>
|
|
161
|
+
}, args => service.compare(args)))
|
|
100
162
|
}
|
package/lib/candidate.js
CHANGED
|
@@ -6,6 +6,16 @@ export const MANIFEST_NAME = 'candidate-manifest.json'
|
|
|
6
6
|
const DIGEST_PREFIX = Buffer.from('harbor-dsh-candidate-v1\0')
|
|
7
7
|
const EXCLUDED_DIRS = new Set(['.git', 'node_modules', '__pycache__'])
|
|
8
8
|
const EXCLUDED_FILES = new Set([MANIFEST_NAME, '.DS_Store'])
|
|
9
|
+
const LOCKFILES = ['package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lock', 'bun.lockb']
|
|
10
|
+
const CREDENTIAL_FILES = new Set([
|
|
11
|
+
'credentials.json',
|
|
12
|
+
'service-account.json',
|
|
13
|
+
'secrets.json',
|
|
14
|
+
'secrets.yaml',
|
|
15
|
+
'secrets.yml',
|
|
16
|
+
'id_rsa',
|
|
17
|
+
'id_ed25519',
|
|
18
|
+
])
|
|
9
19
|
|
|
10
20
|
async function walk(root, current = root) {
|
|
11
21
|
const entries = await readdir(current, { withFileTypes: true })
|
|
@@ -46,8 +56,7 @@ export async function computeCandidate(candidateDir) {
|
|
|
46
56
|
return { digest: `sha256:${digest.digest('hex')}`, files }
|
|
47
57
|
}
|
|
48
58
|
|
|
49
|
-
|
|
50
|
-
const root = path.resolve(candidateDir)
|
|
59
|
+
async function validateCandidateContract(root) {
|
|
51
60
|
for (const required of ['cordis.yml', 'package.json']) {
|
|
52
61
|
try {
|
|
53
62
|
if (!(await stat(path.join(root, required))).isFile()) throw new Error()
|
|
@@ -55,6 +64,24 @@ export async function snapshotCandidate(candidateDir, options = {}) {
|
|
|
55
64
|
throw new Error(`Candidate is missing required file: ${required}`)
|
|
56
65
|
}
|
|
57
66
|
}
|
|
67
|
+
const files = await walk(root)
|
|
68
|
+
if (!files.some(item => LOCKFILES.includes(item.relative))) {
|
|
69
|
+
throw new Error(`Candidate requires a JavaScript lockfile: ${LOCKFILES.join(', ')}`)
|
|
70
|
+
}
|
|
71
|
+
const credentialPaths = files
|
|
72
|
+
.map(item => item.relative)
|
|
73
|
+
.filter((relative) => {
|
|
74
|
+
const name = path.basename(relative).toLowerCase()
|
|
75
|
+
return name.startsWith('.env') || CREDENTIAL_FILES.has(name)
|
|
76
|
+
})
|
|
77
|
+
if (credentialPaths.length > 0) {
|
|
78
|
+
throw new Error(`Candidate contains credential-bearing files: ${credentialPaths.join(', ')}; inject credentials at runtime instead`)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function snapshotCandidate(candidateDir, options = {}) {
|
|
83
|
+
const root = path.resolve(candidateDir)
|
|
84
|
+
await validateCandidateContract(root)
|
|
58
85
|
let packageJson
|
|
59
86
|
try {
|
|
60
87
|
packageJson = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8'))
|