@moreih29/nexus-core 0.9.0 → 0.11.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 +7 -6
- package/conformance/lifecycle/README.md +7 -1
- package/conformance/lifecycle/memory-access-record.json +27 -0
- package/conformance/lifecycle/session-end.json +48 -0
- package/conformance/schema/fixture.schema.json +2 -2
- package/conformance/state-schemas/memory-access.schema.json +36 -0
- package/docs/consumer-implementation-guide.md +58 -45
- package/docs/memory-lifecycle-contract.md +119 -0
- package/docs/nexus-outputs-contract.md +30 -0
- package/manifest.json +130 -33
- package/package.json +1 -1
- package/schema/manifest.schema.json +4 -2
- package/schema/memory-policy.schema.json +98 -0
- package/schema/task-exceptions.schema.json +40 -0
- package/schema/vocabulary.schema.json +27 -2
- package/scripts/lib/validate.ts +42 -4
- package/skills/nx-plan/body.md +14 -5
- package/skills/nx-run/meta.yml +1 -0
- package/vocabulary/invocations.yml +31 -0
- package/vocabulary/memory_policy.yml +88 -0
- package/vocabulary/tags.yml +9 -0
- package/vocabulary/task-exceptions.yml +29 -0
package/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"nexus_core_version": "0.
|
|
3
|
-
"nexus_core_commit": "
|
|
2
|
+
"nexus_core_version": "0.11.0",
|
|
3
|
+
"nexus_core_commit": "7d32c9e06ec206980cbc1399d29c8cb754cd0d5a",
|
|
4
4
|
"schema_contract_version": "2.0",
|
|
5
5
|
"agents": [
|
|
6
6
|
{
|
|
@@ -35,6 +35,20 @@
|
|
|
35
35
|
"id": "designer",
|
|
36
36
|
"body_hash": "sha256:88ac56147d0e5bdf23fa591ce570a9c2d0eb1338df4ec2219f6238ddfcb65df4"
|
|
37
37
|
},
|
|
38
|
+
{
|
|
39
|
+
"name": "engineer",
|
|
40
|
+
"description": "Implementation — writes code, debugs issues, follows specifications from Lead and architect",
|
|
41
|
+
"task": "Code implementation, edits, debugging",
|
|
42
|
+
"alias_ko": "엔지니어",
|
|
43
|
+
"category": "do",
|
|
44
|
+
"resume_tier": "bounded",
|
|
45
|
+
"model_tier": "standard",
|
|
46
|
+
"capabilities": [
|
|
47
|
+
"no_task_create"
|
|
48
|
+
],
|
|
49
|
+
"id": "engineer",
|
|
50
|
+
"body_hash": "sha256:3d58b1b490c2f93cace2eedd0f04ec000f84514388eb086768cf53f8fa33db01"
|
|
51
|
+
},
|
|
38
52
|
{
|
|
39
53
|
"name": "reviewer",
|
|
40
54
|
"description": "Content verification — validates accuracy, checks facts, confirms grammar and format of non-code deliverables",
|
|
@@ -50,20 +64,6 @@
|
|
|
50
64
|
"id": "reviewer",
|
|
51
65
|
"body_hash": "sha256:f04d15249601b14046e7e40a4475defb289436c4474afbd89986964f8c3e7c2f"
|
|
52
66
|
},
|
|
53
|
-
{
|
|
54
|
-
"name": "engineer",
|
|
55
|
-
"description": "Implementation — writes code, debugs issues, follows specifications from Lead and architect",
|
|
56
|
-
"task": "Code implementation, edits, debugging",
|
|
57
|
-
"alias_ko": "엔지니어",
|
|
58
|
-
"category": "do",
|
|
59
|
-
"resume_tier": "bounded",
|
|
60
|
-
"model_tier": "standard",
|
|
61
|
-
"capabilities": [
|
|
62
|
-
"no_task_create"
|
|
63
|
-
],
|
|
64
|
-
"id": "engineer",
|
|
65
|
-
"body_hash": "sha256:3d58b1b490c2f93cace2eedd0f04ec000f84514388eb086768cf53f8fa33db01"
|
|
66
|
-
},
|
|
67
67
|
{
|
|
68
68
|
"name": "strategist",
|
|
69
69
|
"description": "Business strategy — evaluates market positioning, competitive landscape, and business viability of decisions",
|
|
@@ -142,19 +142,6 @@
|
|
|
142
142
|
}
|
|
143
143
|
],
|
|
144
144
|
"skills": [
|
|
145
|
-
{
|
|
146
|
-
"name": "nx-run",
|
|
147
|
-
"description": "Execution — user-directed agent composition.",
|
|
148
|
-
"summary": "Execution — user-directed agent composition",
|
|
149
|
-
"triggers": [
|
|
150
|
-
"run"
|
|
151
|
-
],
|
|
152
|
-
"harness_docs_refs": [
|
|
153
|
-
"resume_invocation"
|
|
154
|
-
],
|
|
155
|
-
"id": "nx-run",
|
|
156
|
-
"body_hash": "sha256:0e2c443efceeab4621709a85cd4e2ba50471d2e850680c655d776cbb62814549"
|
|
157
|
-
},
|
|
158
145
|
{
|
|
159
146
|
"name": "nx-init",
|
|
160
147
|
"description": "Project onboarding — scan, mission, essentials, context generation",
|
|
@@ -188,7 +175,21 @@
|
|
|
188
175
|
"resume_invocation"
|
|
189
176
|
],
|
|
190
177
|
"id": "nx-plan",
|
|
191
|
-
"body_hash": "sha256:
|
|
178
|
+
"body_hash": "sha256:083ce49c06f8d3e4a0299aa8eb8e33460b68d6a277fe356b4db9635c21016aff"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"name": "nx-run",
|
|
182
|
+
"description": "Execution — user-directed agent composition.",
|
|
183
|
+
"summary": "Execution — user-directed agent composition",
|
|
184
|
+
"triggers": [
|
|
185
|
+
"run"
|
|
186
|
+
],
|
|
187
|
+
"harness_docs_refs": [
|
|
188
|
+
"resume_invocation",
|
|
189
|
+
"nexus_hook_mapping"
|
|
190
|
+
],
|
|
191
|
+
"id": "nx-run",
|
|
192
|
+
"body_hash": "sha256:0e2c443efceeab4621709a85cd4e2ba50471d2e850680c655d776cbb62814549"
|
|
192
193
|
}
|
|
193
194
|
],
|
|
194
195
|
"vocabulary": {
|
|
@@ -302,14 +303,16 @@
|
|
|
302
303
|
"trigger": "[m]",
|
|
303
304
|
"type": "inline_action",
|
|
304
305
|
"handler": "memory_store",
|
|
305
|
-
"description": "Stores a lesson or reference to .nexus/memory/"
|
|
306
|
+
"description": "Stores a lesson or reference to .nexus/memory/",
|
|
307
|
+
"prose_guidance": "저장 admission 기준 — 코드/웹에서 다시 얻을 수 없는 정보만 저장한다. memory 파일의 naming, category, lifecycle 운영 정책은 vocabulary/memory_policy.yml과 docs/memory-lifecycle-contract.md를 canonical source로 참조한다.\n"
|
|
306
308
|
},
|
|
307
309
|
{
|
|
308
310
|
"id": "m-gc",
|
|
309
311
|
"trigger": "[m:gc]",
|
|
310
312
|
"type": "inline_action",
|
|
311
313
|
"handler": "memory_gc",
|
|
312
|
-
"description": "Garbage-collects .nexus/memory/ by merging or removing stale entries"
|
|
314
|
+
"description": "Garbage-collects .nexus/memory/ by merging or removing stale entries",
|
|
315
|
+
"prose_guidance": "gc 트리거 조건 평가, merge 판단, forgetting policy 집행은 vocabulary/memory_policy.yml에 정의된 원칙을 따르고 구체 임계값은 consumer-local 설정으로 결정한다.\n"
|
|
313
316
|
},
|
|
314
317
|
{
|
|
315
318
|
"id": "rule",
|
|
@@ -346,7 +349,101 @@
|
|
|
346
349
|
"description": "Ask the user a structured question with selectable options.",
|
|
347
350
|
"intent": "structured_user_prompt",
|
|
348
351
|
"fallback_behavior": "If the harness lacks a structured question tool (e.g., opencode-nexus),\npresent the question as prose followed by the options enumerated as a\nnumbered list, then await the user's free-form reply. The LLM is\nexpected to map the reply to the most appropriate option or treat it\nas a free-form answer if no options were given.\n"
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"id": "memory_read_observation",
|
|
355
|
+
"description": "Harness-local observation of an agent reading a file under .nexus/memory/. This invocation captures the moment a memory file's content is loaded into an agent's context. It is not the read action itself — the read is performed by the harness's file-read primitive — but the observation event emitted by the harness after the read completes.\n",
|
|
356
|
+
"intent": "memory_read_observation",
|
|
357
|
+
"fallback_behavior": "If the harness cannot observe file-read events (no equivalent hook), memory access tracking is unavailable and forgetting decisions must rely on manual inspection via the [m:gc] tag. Automatic deletion policies requiring access metadata are consequently not enabled in such harnesses.\n"
|
|
358
|
+
}
|
|
359
|
+
],
|
|
360
|
+
"task_exceptions": [
|
|
361
|
+
{
|
|
362
|
+
"id": "docs_only.coherent",
|
|
363
|
+
"description": "A batch of documentation files that all address the same scheme, decision, or structural change and should be treated as a single coherent unit.",
|
|
364
|
+
"applies_when": "all changed files are .md or frontmatter-only, and share one common scheme, decision, or structural change",
|
|
365
|
+
"treatment": "bundle into 1 writer task + 1 reviewer pair; file count / line count thresholds waived; state coherence claim in task approach field",
|
|
366
|
+
"rationale": "e.g., updating agent frontmatter files after a new field is introduced; files share the same change intent"
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
"id": "docs_only.independent",
|
|
370
|
+
"description": "A set of documentation files where each file addresses a distinct topic with no cross-reference dependency.",
|
|
371
|
+
"applies_when": "each .md file addresses a distinct topic with no cross-reference dependency",
|
|
372
|
+
"treatment": "N parallel writer tasks (one per file), each paired with 1 reviewer task; file count threshold waived per-task",
|
|
373
|
+
"rationale": "e.g., separate proposal drafts; each file is independently consumed"
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"id": "same_file_bundle",
|
|
377
|
+
"description": "Two or more decomposed sub-tasks that would each modify the same target file, requiring merger to avoid conflicts.",
|
|
378
|
+
"applies_when": "two or more sub-tasks in the decomposition would each modify the same target file",
|
|
379
|
+
"treatment": "merge sub-tasks into a single task under one owner with a structured prompt listing each sub-task's requirements; the merged task counts as 1 task and 1 artifact cluster; does not apply when sub-tasks are sequenced (A's output feeds B) — those remain separate with a deps relationship",
|
|
380
|
+
"rationale": "parallel subagents targeting the same file cause merge conflicts"
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
"id": "generated_artifacts",
|
|
384
|
+
"description": "Files that are build output and do not represent independent authoring decisions.",
|
|
385
|
+
"applies_when": "files are build output (e.g., paths declared as build outputs in the harness's build configuration)",
|
|
386
|
+
"treatment": "excluded from task count, artifact cluster file count, and line count calculations; committed as part of the task that triggers their generation",
|
|
387
|
+
"rationale": "generated files do not represent independent authoring decisions"
|
|
349
388
|
}
|
|
350
|
-
]
|
|
389
|
+
],
|
|
390
|
+
"memory_policy": {
|
|
391
|
+
"categories": [
|
|
392
|
+
{
|
|
393
|
+
"id": "empirical",
|
|
394
|
+
"prefix": "empirical-",
|
|
395
|
+
"description": "Empirically verified findings — observations and measurements that the project has confirmed through its own experimentation. Examples include runtime behavior observations, testing-derived structural facts, and operational measurements that cannot be inferred from documentation alone.\n"
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"id": "external",
|
|
399
|
+
"prefix": "external-",
|
|
400
|
+
"description": "External constraints and references — requirements imposed by upstream dependencies, third-party API limits, vendor documentation quotations, and any knowledge that originates outside the project. May become stale if the upstream source changes.\n"
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
"id": "pattern",
|
|
404
|
+
"prefix": "pattern-",
|
|
405
|
+
"description": "Tactical operational patterns — recurring cycle-level recipes, routing heuristics, and procedural knowledge developed through work on the project. Architectural or design-level patterns belong in .nexus/context/, not here.\n"
|
|
406
|
+
}
|
|
407
|
+
],
|
|
408
|
+
"naming": {
|
|
409
|
+
"pattern": "^[a-z0-9][a-z0-9-]*\\.md$",
|
|
410
|
+
"description": "Memory filenames are lowercase kebab-case .md files. The name should be a descriptive topic of 2–4 words. An optional category prefix from the categories section above may precede the topic. Version numbers and dates must not appear in filenames — temporal information belongs inside the file.\n",
|
|
411
|
+
"optional_prefix": true
|
|
412
|
+
},
|
|
413
|
+
"access_tracking": {
|
|
414
|
+
"observation_primitive": "file_read",
|
|
415
|
+
"scope": ".nexus/memory/",
|
|
416
|
+
"description": "Harnesses observe the moment an agent reads a memory file. Save events, directory scans (glob, grep), and mentions of the path in prose are not observation events. The set of events observed determines the accumulated access record.\n",
|
|
417
|
+
"information_accumulated": [
|
|
418
|
+
{
|
|
419
|
+
"name": "last_access_timestamp",
|
|
420
|
+
"meaning": "Wall-clock time of the most recent read event. Field name is harness-local; the canonical schema for storage is conformance/state-schemas/memory-access.schema.json.\n"
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
"name": "access_count",
|
|
424
|
+
"meaning": "Cumulative count of read events observed for this file since tracking began.\n"
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
"name": "last_reader_identity",
|
|
428
|
+
"meaning": "Identifier of the most recent reader (agent id or equivalent). Harness-local value domain.\n"
|
|
429
|
+
}
|
|
430
|
+
],
|
|
431
|
+
"storage_contract_reference": "conformance/state-schemas/memory-access.schema.json"
|
|
432
|
+
},
|
|
433
|
+
"forgetting": {
|
|
434
|
+
"manual_gate_default": true,
|
|
435
|
+
"description": "Manual gc (triggered by the [m:gc] tag) is the default forgetting path. Automatic deletion is opt-in per consumer and never runs without explicit enablement.\n",
|
|
436
|
+
"automatic_deletion_structure": {
|
|
437
|
+
"principle": "minimum_three_signal_intersection",
|
|
438
|
+
"description": "If a consumer enables automatic deletion, the policy must require the simultaneous satisfaction of at least three independent signals — for example, elapsed time since last access, cycles since last read, and cumulative access count. Single-signal automatic deletion is prohibited. The specific signal thresholds are consumer-local and must be set to match the project's cycle cadence.\n"
|
|
439
|
+
},
|
|
440
|
+
"recoverable_deletion_requirement": "git_commit",
|
|
441
|
+
"recoverable_deletion_description": "Every memory file deletion must be recorded as a git commit. The commit message should include a recovery path that allows the file to be reconstructed via git history. Specific commit message format is consumer-local.\n"
|
|
442
|
+
},
|
|
443
|
+
"merge": {
|
|
444
|
+
"principle": "merge_before_create",
|
|
445
|
+
"description": "When a new memory save candidate substantively overlaps an existing file in topic and category, merging the new content into the existing file is preferred over creating a new file. Specific overlap-detection criteria (for example, keyword match thresholds) are consumer-local.\n"
|
|
446
|
+
}
|
|
447
|
+
}
|
|
351
448
|
}
|
|
352
449
|
}
|
package/package.json
CHANGED
|
@@ -63,13 +63,15 @@
|
|
|
63
63
|
"vocabulary": {
|
|
64
64
|
"type": "object",
|
|
65
65
|
"additionalProperties": false,
|
|
66
|
-
"required": ["capabilities", "categories", "resume_tiers", "tags", "invocations"],
|
|
66
|
+
"required": ["capabilities", "categories", "resume_tiers", "tags", "invocations", "task_exceptions", "memory_policy"],
|
|
67
67
|
"properties": {
|
|
68
68
|
"capabilities": { "type": "array" },
|
|
69
69
|
"categories": { "type": "array" },
|
|
70
70
|
"resume_tiers": { "type": "array" },
|
|
71
71
|
"tags": { "type": "array" },
|
|
72
|
-
"invocations": { "type": "array" }
|
|
72
|
+
"invocations": { "type": "array" },
|
|
73
|
+
"task_exceptions": { "type": "array" },
|
|
74
|
+
"memory_policy": { "type": "object" }
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://moreih29/nexus-core/schema/memory-policy.schema.json",
|
|
4
|
+
"title": "Nexus Memory Policy",
|
|
5
|
+
"description": "Schema for vocabulary/memory_policy.yml — canonical memory policy for .nexus/memory/.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["categories", "naming", "access_tracking", "forgetting", "merge"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"categories": {
|
|
11
|
+
"type": "array",
|
|
12
|
+
"items": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"additionalProperties": false,
|
|
15
|
+
"required": ["id", "prefix", "description"],
|
|
16
|
+
"properties": {
|
|
17
|
+
"id": { "type": "string", "minLength": 1 },
|
|
18
|
+
"prefix": { "type": "string", "minLength": 1 },
|
|
19
|
+
"description": { "type": "string", "minLength": 1 }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"naming": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"additionalProperties": false,
|
|
26
|
+
"required": ["pattern", "description", "optional_prefix"],
|
|
27
|
+
"properties": {
|
|
28
|
+
"pattern": { "type": "string", "minLength": 1 },
|
|
29
|
+
"description": { "type": "string", "minLength": 1 },
|
|
30
|
+
"optional_prefix": { "type": "boolean" }
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"access_tracking": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"additionalProperties": false,
|
|
36
|
+
"required": [
|
|
37
|
+
"observation_primitive",
|
|
38
|
+
"scope",
|
|
39
|
+
"description",
|
|
40
|
+
"information_accumulated",
|
|
41
|
+
"storage_contract_reference"
|
|
42
|
+
],
|
|
43
|
+
"properties": {
|
|
44
|
+
"observation_primitive": { "type": "string", "minLength": 1 },
|
|
45
|
+
"scope": { "type": "string", "minLength": 1 },
|
|
46
|
+
"description": { "type": "string", "minLength": 1 },
|
|
47
|
+
"information_accumulated": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"items": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"additionalProperties": false,
|
|
52
|
+
"required": ["name", "meaning"],
|
|
53
|
+
"properties": {
|
|
54
|
+
"name": { "type": "string", "minLength": 1 },
|
|
55
|
+
"meaning": { "type": "string", "minLength": 1 }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"storage_contract_reference": { "type": "string", "minLength": 1 }
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"forgetting": {
|
|
63
|
+
"type": "object",
|
|
64
|
+
"additionalProperties": false,
|
|
65
|
+
"required": [
|
|
66
|
+
"manual_gate_default",
|
|
67
|
+
"description",
|
|
68
|
+
"automatic_deletion_structure",
|
|
69
|
+
"recoverable_deletion_requirement",
|
|
70
|
+
"recoverable_deletion_description"
|
|
71
|
+
],
|
|
72
|
+
"properties": {
|
|
73
|
+
"manual_gate_default": { "type": "boolean" },
|
|
74
|
+
"description": { "type": "string", "minLength": 1 },
|
|
75
|
+
"automatic_deletion_structure": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"additionalProperties": false,
|
|
78
|
+
"required": ["principle", "description"],
|
|
79
|
+
"properties": {
|
|
80
|
+
"principle": { "type": "string", "minLength": 1 },
|
|
81
|
+
"description": { "type": "string", "minLength": 1 }
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"recoverable_deletion_requirement": { "type": "string", "minLength": 1 },
|
|
85
|
+
"recoverable_deletion_description": { "type": "string", "minLength": 1 }
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"merge": {
|
|
89
|
+
"type": "object",
|
|
90
|
+
"additionalProperties": false,
|
|
91
|
+
"required": ["principle", "description"],
|
|
92
|
+
"properties": {
|
|
93
|
+
"principle": { "type": "string", "minLength": 1 },
|
|
94
|
+
"description": { "type": "string", "minLength": 1 }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://moreih29/nexus-core/schema/task-exceptions.schema.json",
|
|
4
|
+
"title": "Nexus Task Exceptions vocabulary file",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["task_exceptions"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"task_exceptions": {
|
|
10
|
+
"type": "array",
|
|
11
|
+
"items": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"additionalProperties": false,
|
|
14
|
+
"required": ["id", "description", "applies_when", "treatment", "rationale"],
|
|
15
|
+
"properties": {
|
|
16
|
+
"id": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)?$"
|
|
19
|
+
},
|
|
20
|
+
"description": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"minLength": 1
|
|
23
|
+
},
|
|
24
|
+
"applies_when": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"minLength": 1
|
|
27
|
+
},
|
|
28
|
+
"treatment": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"minLength": 1
|
|
31
|
+
},
|
|
32
|
+
"rationale": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"minLength": 1
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -70,7 +70,8 @@
|
|
|
70
70
|
"description": { "$ref": "common.schema.json#/$defs/description" },
|
|
71
71
|
"skill": { "$ref": "common.schema.json#/$defs/id" },
|
|
72
72
|
"handler": { "type": "string", "minLength": 1 },
|
|
73
|
-
"variants": { "type": "array", "items": { "type": "string", "minLength": 1 } }
|
|
73
|
+
"variants": { "type": "array", "items": { "type": "string", "minLength": 1 } },
|
|
74
|
+
"prose_guidance": { "type": "string", "minLength": 40 }
|
|
74
75
|
},
|
|
75
76
|
"if": {
|
|
76
77
|
"properties": { "type": { "const": "skill" } },
|
|
@@ -137,6 +138,30 @@
|
|
|
137
138
|
"properties": {
|
|
138
139
|
"invocations": { "type": "array", "items": { "$ref": "#/$defs/invocationEntry" } }
|
|
139
140
|
}
|
|
140
|
-
}
|
|
141
|
+
},
|
|
142
|
+
"taskExceptionEntry": {
|
|
143
|
+
"type": "object",
|
|
144
|
+
"additionalProperties": false,
|
|
145
|
+
"required": ["id", "description", "applies_when", "treatment", "rationale"],
|
|
146
|
+
"properties": {
|
|
147
|
+
"id": {
|
|
148
|
+
"type": "string",
|
|
149
|
+
"pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)?$"
|
|
150
|
+
},
|
|
151
|
+
"description": { "type": "string", "minLength": 1 },
|
|
152
|
+
"applies_when": { "type": "string", "minLength": 1 },
|
|
153
|
+
"treatment": { "type": "string", "minLength": 1 },
|
|
154
|
+
"rationale": { "type": "string", "minLength": 1 }
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
"taskExceptionFile": {
|
|
158
|
+
"type": "object",
|
|
159
|
+
"additionalProperties": false,
|
|
160
|
+
"required": ["task_exceptions"],
|
|
161
|
+
"properties": {
|
|
162
|
+
"task_exceptions": { "type": "array", "items": { "$ref": "#/$defs/taskExceptionEntry" } }
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
"memoryPolicyFile": { "$ref": "memory-policy.schema.json" }
|
|
141
166
|
}
|
|
142
167
|
}
|
package/scripts/lib/validate.ts
CHANGED
|
@@ -81,12 +81,30 @@ interface InvocationEntry {
|
|
|
81
81
|
fallback_behavior: string;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
interface TaskExceptionEntry {
|
|
85
|
+
id: string;
|
|
86
|
+
description: string;
|
|
87
|
+
applies_when: string;
|
|
88
|
+
treatment: string;
|
|
89
|
+
rationale: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface MemoryPolicy {
|
|
93
|
+
categories: unknown[];
|
|
94
|
+
naming: unknown;
|
|
95
|
+
access_tracking: unknown;
|
|
96
|
+
forgetting: unknown;
|
|
97
|
+
merge: unknown;
|
|
98
|
+
}
|
|
99
|
+
|
|
84
100
|
interface Vocab {
|
|
85
101
|
capabilities: CapabilityEntry[];
|
|
86
102
|
categories: SimpleEntry[];
|
|
87
103
|
resume_tiers: SimpleEntry[];
|
|
88
104
|
tags: TagEntry[];
|
|
89
105
|
invocations: InvocationEntry[];
|
|
106
|
+
task_exceptions: TaskExceptionEntry[];
|
|
107
|
+
memory_policy: MemoryPolicy;
|
|
90
108
|
}
|
|
91
109
|
|
|
92
110
|
interface ManifestAgent extends AgentMeta {
|
|
@@ -116,6 +134,8 @@ interface Manifest {
|
|
|
116
134
|
resume_tiers: SimpleEntry[];
|
|
117
135
|
tags: TagEntry[];
|
|
118
136
|
invocations: ManifestInvocationEntry[];
|
|
137
|
+
task_exceptions: TaskExceptionEntry[];
|
|
138
|
+
memory_policy: MemoryPolicy;
|
|
119
139
|
};
|
|
120
140
|
}
|
|
121
141
|
|
|
@@ -129,6 +149,8 @@ interface LoadedSchemas {
|
|
|
129
149
|
skillValidator: ValidateFunction;
|
|
130
150
|
vocabValidator: ValidateFunction;
|
|
131
151
|
manifestValidator: ValidateFunction;
|
|
152
|
+
taskExceptionValidator: ValidateFunction;
|
|
153
|
+
memoryPolicyValidator: ValidateFunction;
|
|
132
154
|
}
|
|
133
155
|
|
|
134
156
|
let cachedSchemas: LoadedSchemas | null = null;
|
|
@@ -152,12 +174,14 @@ export async function loadSchemas(root: string): Promise<void> {
|
|
|
152
174
|
addFormats(ajv);
|
|
153
175
|
ajvErrors(ajv);
|
|
154
176
|
|
|
155
|
-
const [commonRaw, agentRaw, skillRaw, vocabRaw, manifestRaw] = await Promise.all([
|
|
177
|
+
const [commonRaw, agentRaw, skillRaw, vocabRaw, manifestRaw, taskExceptionSchemaRaw, memoryPolicySchemaRaw] = await Promise.all([
|
|
156
178
|
readFile(path.join(schemaDir, 'common.schema.json'), 'utf8'),
|
|
157
179
|
readFile(path.join(schemaDir, 'agent.schema.json'), 'utf8'),
|
|
158
180
|
readFile(path.join(schemaDir, 'skill.schema.json'), 'utf8'),
|
|
159
181
|
readFile(path.join(schemaDir, 'vocabulary.schema.json'), 'utf8'),
|
|
160
182
|
readFile(path.join(schemaDir, 'manifest.schema.json'), 'utf8'),
|
|
183
|
+
readFile(path.join(schemaDir, 'task-exceptions.schema.json'), 'utf8'),
|
|
184
|
+
readFile(path.join(schemaDir, 'memory-policy.schema.json'), 'utf8'),
|
|
161
185
|
]);
|
|
162
186
|
|
|
163
187
|
const commonSchema = JSON.parse(commonRaw) as Record<string, unknown>;
|
|
@@ -165,6 +189,8 @@ export async function loadSchemas(root: string): Promise<void> {
|
|
|
165
189
|
const skillSchema = JSON.parse(skillRaw) as Record<string, unknown>;
|
|
166
190
|
const vocabSchema = JSON.parse(vocabRaw) as Record<string, unknown>;
|
|
167
191
|
const manifestSchema = JSON.parse(manifestRaw) as Record<string, unknown>;
|
|
192
|
+
const taskExceptionSchema = JSON.parse(taskExceptionSchemaRaw) as Record<string, unknown>;
|
|
193
|
+
const memoryPolicySchema = JSON.parse(memoryPolicySchemaRaw) as Record<string, unknown>;
|
|
168
194
|
|
|
169
195
|
ajv.addSchema(commonSchema);
|
|
170
196
|
ajv.addSchema(vocabSchema);
|
|
@@ -210,6 +236,8 @@ export async function loadSchemas(root: string): Promise<void> {
|
|
|
210
236
|
const resumeTierValidator = await ajv.compileAsync(resumeTierFileSchema);
|
|
211
237
|
const tagValidator = await ajv.compileAsync(tagFileSchema);
|
|
212
238
|
const invocationValidator = await ajv.compileAsync(invocationFileSchema);
|
|
239
|
+
const taskExceptionValidator = await ajv.compileAsync(taskExceptionSchema);
|
|
240
|
+
const memoryPolicyValidator = await ajv.compileAsync(memoryPolicySchema);
|
|
213
241
|
|
|
214
242
|
const manifestAjv = new Ajv2020({
|
|
215
243
|
strict: false,
|
|
@@ -233,10 +261,12 @@ export async function loadSchemas(root: string): Promise<void> {
|
|
|
233
261
|
// Store vocabulary validators and manifest as composite
|
|
234
262
|
vocabValidator: capabilityValidator, // placeholder — we handle vocab separately below
|
|
235
263
|
manifestValidator,
|
|
264
|
+
taskExceptionValidator,
|
|
265
|
+
memoryPolicyValidator,
|
|
236
266
|
};
|
|
237
267
|
|
|
238
268
|
// Store all vocab validators for internal use
|
|
239
|
-
_vocabValidators = { capabilityValidator, categoryValidator, resumeTierValidator, tagValidator, invocationValidator };
|
|
269
|
+
_vocabValidators = { capabilityValidator, categoryValidator, resumeTierValidator, tagValidator, invocationValidator, taskExceptionValidator, memoryPolicyValidator };
|
|
240
270
|
}
|
|
241
271
|
|
|
242
272
|
interface VocabValidators {
|
|
@@ -245,6 +275,8 @@ interface VocabValidators {
|
|
|
245
275
|
resumeTierValidator: ValidateFunction;
|
|
246
276
|
tagValidator: ValidateFunction;
|
|
247
277
|
invocationValidator: ValidateFunction;
|
|
278
|
+
taskExceptionValidator: ValidateFunction;
|
|
279
|
+
memoryPolicyValidator: ValidateFunction;
|
|
248
280
|
}
|
|
249
281
|
|
|
250
282
|
let _vocabValidators: VocabValidators | null = null;
|
|
@@ -559,15 +591,17 @@ async function loadVocab(root: string): Promise<{ vocab: Vocab | null; results:
|
|
|
559
591
|
return data as T;
|
|
560
592
|
}
|
|
561
593
|
|
|
562
|
-
const [capData, catData, resumeData, tagData, invocationData] = await Promise.all([
|
|
594
|
+
const [capData, catData, resumeData, tagData, invocationData, taskExceptionData, memoryPolicyData] = await Promise.all([
|
|
563
595
|
loadYaml<{ capabilities: CapabilityEntry[] }>('capabilities.yml', _vocabValidators.capabilityValidator),
|
|
564
596
|
loadYaml<{ categories: SimpleEntry[] }>('categories.yml', _vocabValidators.categoryValidator),
|
|
565
597
|
loadYaml<{ resume_tiers: SimpleEntry[] }>('resume-tiers.yml', _vocabValidators.resumeTierValidator),
|
|
566
598
|
loadYaml<{ tags: TagEntry[] }>('tags.yml', _vocabValidators.tagValidator),
|
|
567
599
|
loadYaml<{ invocations: InvocationEntry[] }>('invocations.yml', _vocabValidators.invocationValidator),
|
|
600
|
+
loadYaml<{ task_exceptions: TaskExceptionEntry[] }>('task-exceptions.yml', _vocabValidators.taskExceptionValidator),
|
|
601
|
+
loadYaml<MemoryPolicy>('memory_policy.yml', _vocabValidators.memoryPolicyValidator),
|
|
568
602
|
]);
|
|
569
603
|
|
|
570
|
-
if (!capData || !catData || !resumeData || !tagData || !invocationData) {
|
|
604
|
+
if (!capData || !catData || !resumeData || !tagData || !invocationData || !taskExceptionData || !memoryPolicyData) {
|
|
571
605
|
return { vocab: null, results };
|
|
572
606
|
}
|
|
573
607
|
|
|
@@ -578,6 +612,8 @@ async function loadVocab(root: string): Promise<{ vocab: Vocab | null; results:
|
|
|
578
612
|
resume_tiers: resumeData.resume_tiers,
|
|
579
613
|
tags: tagData.tags,
|
|
580
614
|
invocations: invocationData.invocations,
|
|
615
|
+
task_exceptions: taskExceptionData.task_exceptions,
|
|
616
|
+
memory_policy: memoryPolicyData,
|
|
581
617
|
},
|
|
582
618
|
results,
|
|
583
619
|
};
|
|
@@ -636,6 +672,8 @@ export async function generateManifest(
|
|
|
636
672
|
resume_tiers: vocab.resume_tiers,
|
|
637
673
|
tags: vocab.tags,
|
|
638
674
|
invocations: invocationSummaries,
|
|
675
|
+
task_exceptions: vocab.task_exceptions,
|
|
676
|
+
memory_policy: vocab.memory_policy,
|
|
639
677
|
},
|
|
640
678
|
};
|
|
641
679
|
}
|
package/skills/nx-plan/body.md
CHANGED
|
@@ -220,12 +220,21 @@ All issues decided → generate the plan document (tasks.json) immediately:
|
|
|
220
220
|
| Design analysis / review | **architect** etc. HOW | Technical trade-off judgment |
|
|
221
221
|
| Sequential edits to same file | **lead** | Parallel subagents risk conflict |
|
|
222
222
|
|
|
223
|
-
**
|
|
224
|
-
- Any task with `owner: "engineer"` + `acceptance` field → pair a **tester** task (verify acceptance criteria)
|
|
225
|
-
- Any task with `owner: "writer"` → pair a **reviewer** task (verify deliverable)
|
|
226
|
-
- Paired verification tasks are linked via `deps` to the original task
|
|
223
|
+
**Primary metric — artifact-coherence**: a well-scoped task targets a single artifact or a tightly coupled artifact cluster and makes a single coherent change. A change is coherent when (a) it can be described in one sentence, (b) reverting it leaves all other artifacts consistent, and (c) its acceptance can be verified by inspecting its outputs alone.
|
|
227
224
|
|
|
228
|
-
**
|
|
225
|
+
**Verification auto-pairing (conditional)** — create a CHECK task only when the DO task's acceptance includes the appropriate verification trigger:
|
|
226
|
+
- `owner: "engineer"` + acceptance contains a runtime-behavior criterion → pair a **tester** task.
|
|
227
|
+
- `owner: "writer"` + acceptance contains a verifiable deliverable criterion → pair a **reviewer** task.
|
|
228
|
+
- Exclusions: pure refactor (behavior-preserving), type-only changes, docs-adjacent tasks (.md or frontmatter-only, classified under `docs_only` entries in `vocabulary/task-exceptions.yml`), and researcher tasks. Researcher tasks never receive an auto-paired CHECK — research outputs feed Lead or HOW agents directly, not tester or reviewer.
|
|
229
|
+
- Paired verification tasks are linked via `deps` to the original task.
|
|
230
|
+
|
|
231
|
+
**Exception catalog**: task decomposition exceptions are defined in `vocabulary/task-exceptions.yml` (`docs_only.coherent`, `docs_only.independent`, `same_file_bundle`, `generated_artifacts`). When an exception applies to a task, record its id in the task's `context` field so downstream tooling can trace the classification.
|
|
232
|
+
|
|
233
|
+
**Dedup Layer 1 (plan-time static merge)**: before finalizing the task list, scan draft tasks for overlapping target_files. Overlapping tasks are merged into a single owner task via the `same_file_bundle` exception from `vocabulary/task-exceptions.yml` to prevent parallel write conflicts during execution.
|
|
234
|
+
|
|
235
|
+
**DO/CHECK decomposition principle**: DO agents (engineer, writer, researcher) and CHECK agents (tester, reviewer) accumulate less per-task context than HOW agents. When a task involves multiple independent artifacts, decompose across multiple parallel DO/CHECK subagents rather than bundling. HOW agents benefit from consolidated context and should generally remain as single sessions. Parallel decomposition is worthwhile when there are at least three independent artifacts; below that, bundling under one owner is preferable to avoid parallelization overhead.
|
|
236
|
+
|
|
237
|
+
**HOW decomposition rule**: split HOW analysis across multiple subagents only when the issue crosses different rows of the domain-agent mapping table (architect vs designer vs strategist vs postdoc). Sub-concerns within a single domain row belong in one HOW session.
|
|
229
238
|
|
|
230
239
|
4. **Populate tasks.json** via `nx_task_add`:
|
|
231
240
|
- Set `goal` from the plan topic
|
package/skills/nx-run/meta.yml
CHANGED
|
@@ -114,3 +114,34 @@ invocations:
|
|
|
114
114
|
numbered list, then await the user's free-form reply. The LLM is
|
|
115
115
|
expected to map the reply to the most appropriate option or treat it
|
|
116
116
|
as a free-form answer if no options were given.
|
|
117
|
+
|
|
118
|
+
- id: memory_read_observation
|
|
119
|
+
description: >
|
|
120
|
+
Harness-local observation of an agent reading a file under .nexus/memory/.
|
|
121
|
+
This invocation captures the moment a memory file's content is loaded into
|
|
122
|
+
an agent's context. It is not the read action itself — the read is performed
|
|
123
|
+
by the harness's file-read primitive — but the observation event emitted by
|
|
124
|
+
the harness after the read completes.
|
|
125
|
+
intent: memory_read_observation
|
|
126
|
+
semantic_params:
|
|
127
|
+
- name: file_path
|
|
128
|
+
description: Absolute or project-relative path to the memory file that was read.
|
|
129
|
+
required: true
|
|
130
|
+
- name: reader_identity
|
|
131
|
+
description: Opaque identifier of the agent or subject that performed the read.
|
|
132
|
+
required: true
|
|
133
|
+
- name: observed_at
|
|
134
|
+
description: ISO 8601 timestamp of the observation event.
|
|
135
|
+
required: true
|
|
136
|
+
prose_guidance: >
|
|
137
|
+
Harnesses that wish to track memory access to support informed gc decisions
|
|
138
|
+
should implement this observation by hooking into their filesystem read
|
|
139
|
+
primitive and emitting the event after a read under .nexus/memory/ completes.
|
|
140
|
+
Writes and directory scans are excluded. The accumulated records are persisted
|
|
141
|
+
per conformance/state-schemas/memory-access.schema.json at
|
|
142
|
+
.nexus/state/{harness_id}/memory-access.jsonl.
|
|
143
|
+
fallback_behavior: >
|
|
144
|
+
If the harness cannot observe file-read events (no equivalent hook), memory
|
|
145
|
+
access tracking is unavailable and forgetting decisions must rely on manual
|
|
146
|
+
inspection via the [m:gc] tag. Automatic deletion policies requiring access
|
|
147
|
+
metadata are consequently not enabled in such harnesses.
|