@moreih29/nexus-core 0.8.0 → 0.10.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/conformance/state-schemas/memory-access.schema.json +36 -0
- package/docs/consumer-implementation-guide.md +7 -10
- package/docs/memory-lifecycle-contract.md +119 -0
- package/docs/nexus-layout.md +12 -10
- package/docs/nexus-outputs-contract.md +34 -7
- package/docs/nexus-state-overview.md +1 -1
- package/docs/nexus-tools-contract.md +3 -2
- package/manifest.json +149 -53
- 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/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
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://moreih29/nexus-core/conformance/state-schemas/memory-access.schema.json",
|
|
4
|
+
"title": "Memory Access Record",
|
|
5
|
+
"description": "Schema for a single line in .nexus/state/{harness_id}/memory-access.jsonl — each JSONL line is a JSON object satisfying this schema. Upsert key is `path`.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["path", "last_accessed_ts", "access_count", "last_agent"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"path": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"minLength": 1,
|
|
13
|
+
"description": "Absolute path to the memory file that was read. Typically resolved under .nexus/memory/."
|
|
14
|
+
},
|
|
15
|
+
"last_accessed_ts": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"format": "date-time",
|
|
18
|
+
"description": "ISO 8601 timestamp of the most recent read event for this file."
|
|
19
|
+
},
|
|
20
|
+
"access_count": {
|
|
21
|
+
"type": "integer",
|
|
22
|
+
"minimum": 0,
|
|
23
|
+
"description": "Cumulative count of read events observed for this file since tracking began in this harness."
|
|
24
|
+
},
|
|
25
|
+
"last_agent": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"minLength": 1,
|
|
28
|
+
"description": "Opaque harness-specific identifier for the agent (or equivalent subject) that performed the most recent read."
|
|
29
|
+
},
|
|
30
|
+
"schema_version": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"pattern": "^\\d+\\.\\d+$",
|
|
33
|
+
"description": "Optional migration anchor, aligned with other state schemas introduced in v0.5.0."
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -160,8 +160,6 @@ Nexus state is split into two categories with different scopes, persistence, and
|
|
|
160
160
|
│ ├── plan.json
|
|
161
161
|
│ ├── tasks.json
|
|
162
162
|
│ ├── tool-log.jsonl
|
|
163
|
-
│ ├── edit-tracker.json
|
|
164
|
-
│ ├── reopen-tracker.json
|
|
165
163
|
│ ├── artifacts/
|
|
166
164
|
│ └── {harness-id}/ ← harness namespace (see §Harness-local State Extension)
|
|
167
165
|
│ └── agent-tracker.json
|
|
@@ -193,7 +191,6 @@ At session start, your harness must:
|
|
|
193
191
|
| `state/tasks.json` | Session | No | `task_add` tool (first call) | `task_close` tool |
|
|
194
192
|
| `state/{harness-id}/agent-tracker.json` | Session | No | session_start hook | session_end hook |
|
|
195
193
|
| `state/tool-log.jsonl` | Session | No | post_tool_use hook | session_end hook |
|
|
196
|
-
| `state/edit-tracker.json` | Session | No | post_tool_use hook (first edit) | task_close / session_end |
|
|
197
194
|
| `history.json` | Project | Yes | `plan_start` or `task_close` (first archive) | Never |
|
|
198
195
|
|
|
199
196
|
### Schema validation
|
|
@@ -235,7 +232,7 @@ For every tool, implement exactly the parameter schema, return shape, and side e
|
|
|
235
232
|
Specific requirements to enforce:
|
|
236
233
|
|
|
237
234
|
- `plan_start`: when a prior `plan.json` exists, archive it to `history.json` before creating the new session. Failure to archive on replace will cause data loss.
|
|
238
|
-
- `task_close`: delete `plan.json
|
|
235
|
+
- `task_close`: delete `plan.json` and `tasks.json` after archiving. Leaving these files causes stale state on next session. Files such as `edit-tracker.json` and `reopen-tracker.json` are harness-local concerns and are not managed by `task_close`; delete them in your session_end hook if your harness maintains them.
|
|
239
236
|
- `artifact_write`: create `.nexus/state/artifacts/` on demand if it does not exist. Do not require the directory to pre-exist.
|
|
240
237
|
- `context`: return `{ active: false }` (for plan_status) or `{ exists: false }` (for task_list) when the relevant state file is absent. Do not return an error.
|
|
241
238
|
|
|
@@ -567,8 +564,8 @@ Identify the equivalent events in your harness's plugin system and implement the
|
|
|
567
564
|
|
|
568
565
|
**Expected consumer behavior:**
|
|
569
566
|
- Update `.nexus/state/{harness-id}/agent-tracker.json`: set `status=completed`, record `stopped_at` timestamp.
|
|
570
|
-
- Compute `files_touched` from your tool-log or the subagent's tool usage record. Record which files were created or modified.
|
|
571
|
-
-
|
|
567
|
+
- Compute `files_touched` from your tool-log or the subagent's tool usage record. Record which files were created or modified in the `files_touched` array of the agent's `agent-tracker.json` entry. This field is the authoritative source for bounded-tier resume evaluation.
|
|
568
|
+
- (Optional, harness-local) If your harness maintains a separate `edit-tracker.json` for cross-session file-touch history, update it here. This is not a nexus-core requirement; it is a harness-local optimization.
|
|
572
569
|
- Check if the completed task has pending acceptance criteria that were not verified. If the task has `acceptance` defined and no `tester` or `reviewer` subagent has been scheduled, surface a reminder to Lead.
|
|
573
570
|
- Update the task status in `tasks.json` via the `task_update` tool: set to `completed`.
|
|
574
571
|
|
|
@@ -593,7 +590,7 @@ Read-only tools (query tools, status reads) are never blocked by capability gate
|
|
|
593
590
|
|
|
594
591
|
**Expected consumer behavior:**
|
|
595
592
|
- Append a log entry to `.nexus/state/tool-log.jsonl`: timestamp, agent_id, tool name, file path (if a file was touched), result status.
|
|
596
|
-
-
|
|
593
|
+
- (Optional, harness-local) If your harness maintains `edit-tracker.json`, record the file path and agent_id here. This is a harness-local optimization and is not required by nexus-core. The `files_touched` array in `agent-tracker.json` is the nexus-core-defined record of which files an agent touched.
|
|
597
594
|
- If the tool result indicates an error, record the error in the log for diagnostic purposes. Do not suppress error results.
|
|
598
595
|
|
|
599
596
|
---
|
|
@@ -704,8 +701,8 @@ Before attempting to resume, verify that your harness supports the resume mechan
|
|
|
704
701
|
|
|
705
702
|
For `bounded` agents evaluating resume eligibility:
|
|
706
703
|
- Check `.nexus/state/{harness-id}/agent-tracker.json` for the prior agent ID assigned to this task.
|
|
707
|
-
-
|
|
708
|
-
- If
|
|
704
|
+
- Scan the `files_touched` arrays of all other completed agents in `agent-tracker.json`. If any other agent has touched the same target files since the last session, treat that as an intervening edit and use a fresh spawn regardless of `owner_reuse_policy`. The `files_touched` field is defined in `state-schemas/agent-tracker.schema.json` and is the nexus-core-defined source for this check.
|
|
705
|
+
- If your harness additionally maintains `edit-tracker.json` for finer-grained cross-session tracking, you may consult it as a supplemental heuristic, but it is not required by nexus-core.
|
|
709
706
|
|
|
710
707
|
Full resume tier definitions are in [behavioral-contracts.md §3](./behavioral-contracts.md).
|
|
711
708
|
|
|
@@ -807,7 +804,7 @@ Implement tag detection in your `user_message` hook. When `[plan]` is detected,
|
|
|
807
804
|
|-------|--------------------------|
|
|
808
805
|
| `session_start` | Create `.nexus/state/{harness-id}/` directory; initialize `agent-tracker.json` as `[]` |
|
|
809
806
|
| `user_message` | Detect `[plan]` tag; load `skills/nx-plan/body.md`; inject into Lead's context |
|
|
810
|
-
| `session_end` | Check for `tasks.json`; if present with incomplete tasks, warn the user |
|
|
807
|
+
| `session_end` | Check for `tasks.json`; if present with incomplete tasks, warn the user. If the harness maintains local tracker files (e.g. `edit-tracker.json`, `reopen-tracker.json`), delete them here — see the `session_end` subsection in §9 for full details. |
|
|
811
808
|
|
|
812
809
|
### State files (3 minimum)
|
|
813
810
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Memory Lifecycle Contract
|
|
2
|
+
|
|
3
|
+
This document defines the canonical operating principles for the `.nexus/memory/` layer. It sits alongside `vocabulary/memory_policy.yml`, which is the machine-readable form of the same rules. Where `memory_policy.yml` states principles as structured data for consumer tooling to parse, this document provides authoritative prose for implementers. The boundary between canonical and consumer-local is consistent across both: nexus-core owns the principles; consumers own all thresholds, formats, and enforcement mechanics.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Canonical Principles
|
|
8
|
+
|
|
9
|
+
### 1.1 Read-Event Observation
|
|
10
|
+
|
|
11
|
+
An agent observes a memory file at the moment it reads that file's contents. That read event — and only that event — is the unit of observation for access tracking.
|
|
12
|
+
|
|
13
|
+
Write events, directory scans (glob, grep), and path mentions in prose are not observation events. An agent that lists `.nexus/memory/` to decide which file to read has not yet observed anything. The observation is recorded when the file is opened and its content is consumed.
|
|
14
|
+
|
|
15
|
+
This distinction matters because memory is meant to capture what has actually been used, not what has been found or referenced. Stale detection and gc decisions depend on a signal that reflects genuine use, not incidental proximity.
|
|
16
|
+
|
|
17
|
+
### 1.2 Three Information Types Accumulated
|
|
18
|
+
|
|
19
|
+
For each memory file, three pieces of information are accumulated across read events: (1) the wall-clock time of the most recent read, (2) the cumulative count of reads observed since tracking began, and (3) the identity of the most recent reader.
|
|
20
|
+
|
|
21
|
+
These three values constitute the access record for a file. They are stored in `.nexus/state/{harness_id}/memory-access.jsonl` — one JSONL line per file, upserted on each observation. The canonical field names and types are defined in `conformance/state-schemas/memory-access.schema.json`; that schema is the authoritative source for storage format. Value domains for the reader identity field are harness-local.
|
|
22
|
+
|
|
23
|
+
Together, these three values give consumers the signals they need to reason about whether a memory file remains actively useful or has drifted into disuse.
|
|
24
|
+
|
|
25
|
+
### 1.3 Manual Gate as Default
|
|
26
|
+
|
|
27
|
+
Automatic deletion is off by default. The normal path for removing stale memory files is the `[m:gc]` tag, which the user invokes manually. User intent is the final arbiter of what gets removed.
|
|
28
|
+
|
|
29
|
+
Automatic deletion is an opt-in capability. A consumer may enable it, but must do so explicitly. No consumer should assume automatic deletion is active unless they have deliberately configured it.
|
|
30
|
+
|
|
31
|
+
This default reflects a conservative stance: the cost of accidentally losing a still-relevant memory file is higher than the cost of retaining an unused one. Manual gc preserves human judgment in the loop.
|
|
32
|
+
|
|
33
|
+
### 1.4 Three-Signal Intersection for Automatic Deletion
|
|
34
|
+
|
|
35
|
+
When a consumer enables automatic deletion, the policy must require the simultaneous satisfaction of at least three independent signals before a file is eligible for removal. No single signal, however strong, is sufficient on its own.
|
|
36
|
+
|
|
37
|
+
The three signals should be drawn from independent dimensions — for example: elapsed time since last access, number of work cycles completed since last read, and cumulative access count since tracking began. Requiring intersection across independent dimensions reduces the risk of false positives caused by a single anomalous period (a long vacation, an unusually dense sprint, an early-lifecycle file that has never yet been needed).
|
|
38
|
+
|
|
39
|
+
The specific thresholds for each signal are consumer-local, calibrated to the project's cycle cadence and team working patterns. nexus-core specifies the structural requirement — three independent signals — not the magnitudes.
|
|
40
|
+
|
|
41
|
+
### 1.5 Git-Backed Recoverable Deletion
|
|
42
|
+
|
|
43
|
+
Every memory file deletion must be recorded as a git commit. Deletion without a corresponding commit is not permitted.
|
|
44
|
+
|
|
45
|
+
The commit should include enough information in its message that a reader can reconstruct the recovery path — for example, the git command needed to restore the file from history. Including an explicit recovery path in the commit message is recommended; it reduces the cognitive load on anyone who later discovers the deletion was premature. The exact format of the commit message is consumer-local.
|
|
46
|
+
|
|
47
|
+
This requirement ensures that no memory deletion is silent. The project history serves as a safety net, and the act of committing forces an intentional moment before content is removed from the active workspace.
|
|
48
|
+
|
|
49
|
+
### 1.6 Merge-Before-Create
|
|
50
|
+
|
|
51
|
+
When a new memory save candidate substantively overlaps an existing file in topic and category, the existing file should be extended rather than a new file created. Proliferation of near-duplicate files degrades the utility of the memory layer: duplicates force readers to reconcile redundant content and make gc harder to reason about.
|
|
52
|
+
|
|
53
|
+
The concrete criteria for deciding whether two topics overlap — keyword thresholds, semantic distance, structural similarity — are consumer-local. nexus-core requires the preference for merging; it does not specify the matching algorithm.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 2. Category Boundaries
|
|
58
|
+
|
|
59
|
+
Memory files are organized into three categories defined in `vocabulary/memory_policy.yml`. Each category has a `prefix-` naming convention that makes the file's type visible in directory listings.
|
|
60
|
+
|
|
61
|
+
### 2.1 `empirical-`
|
|
62
|
+
|
|
63
|
+
Files in this category contain empirically verified findings: observations and measurements 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.
|
|
64
|
+
|
|
65
|
+
Empirical memory captures what the project has learned by doing. It is distinct from external references (what others have said) and from patterns (what the project has found effective as procedure).
|
|
66
|
+
|
|
67
|
+
### 2.2 `external-`
|
|
68
|
+
|
|
69
|
+
Files in this category contain external constraints and references: requirements imposed by upstream dependencies, third-party API limits, vendor documentation quotations, and knowledge that originates outside the project.
|
|
70
|
+
|
|
71
|
+
External memory may become stale if the upstream source changes. This is the category most likely to require periodic review against current upstream state.
|
|
72
|
+
|
|
73
|
+
### 2.3 `pattern-`
|
|
74
|
+
|
|
75
|
+
Files in this category contain tactical operational patterns: recurring cycle-level recipes, routing heuristics, and procedural knowledge developed through work on the project.
|
|
76
|
+
|
|
77
|
+
The scope of this category is explicitly tactical. Architectural or design-level patterns do not belong here. If a finding rises to the level of architectural principle or design rationale — something that shapes how the project is structured rather than how day-to-day work is executed — it belongs in `.nexus/context/`, not in `memory/`.
|
|
78
|
+
|
|
79
|
+
### 2.4 Relation to `context/` and `rules/`
|
|
80
|
+
|
|
81
|
+
`.nexus/memory/` and `.nexus/context/` serve different purposes and should not be confused.
|
|
82
|
+
|
|
83
|
+
`memory/` holds project-accumulated working knowledge: empirical findings, external references, and tactical patterns that agents draw on during active work. These files are created via `[m]` and managed via `[m:gc]`. They are subject to the gc lifecycle defined in this document.
|
|
84
|
+
|
|
85
|
+
`.nexus/context/` holds design principles, architectural philosophy, and onboarding materials — documents that define the project's enduring structure and intent. Primer-style documents (documents that introduce the project's goals, vocabulary, or design decisions to a new reader) belong in `context/`, not in `memory/`. The gc lifecycle does not apply to `context/` files; they are maintained by `[sync]` and represent stable project knowledge rather than accumulated working observations.
|
|
86
|
+
|
|
87
|
+
`.nexus/rules/` holds enforceable project rules. These are not memory entries and are not subject to this lifecycle.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 3. Consumer Responsibility
|
|
92
|
+
|
|
93
|
+
The principles in §1 are canonical. Everything below is consumer-local — decisions that each consumer makes independently, calibrated to their own project, harness, and team cadence. nexus-core does not prescribe values for any of the following items.
|
|
94
|
+
|
|
95
|
+
Consumers determine:
|
|
96
|
+
|
|
97
|
+
- The specific threshold for each signal used in automatic deletion (for example: how much time elapsed, how many cycles completed, what access count constitutes "unused")
|
|
98
|
+
- File and directory size criteria, if any, used in gc decisions
|
|
99
|
+
- The frequency or trigger conditions for `[m:gc]` invocations in normal workflow
|
|
100
|
+
- The git commit message format for deletion commits, beyond the recommendation that a recovery path be included
|
|
101
|
+
- Whether access counts are re-incremented in resumed sessions (i.e., whether a resumed context that re-reads a file adds to the count or not)
|
|
102
|
+
- The keyword overlap threshold, semantic distance measure, or other matching criteria used to decide whether merge-before-create applies
|
|
103
|
+
- Whether additional filename prefix categories beyond the canonical three are introduced for project-specific use
|
|
104
|
+
|
|
105
|
+
Consumers may configure automatic deletion, but must not treat it as active unless they have explicitly opted in. All other gc path decisions remain under user control by default.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 4. Reference
|
|
110
|
+
|
|
111
|
+
Related vocabulary and files:
|
|
112
|
+
|
|
113
|
+
- `vocabulary/memory_policy.yml` — machine-readable canonical form of the principles in this document
|
|
114
|
+
- `vocabulary/tags.yml` — `[m]` and `[m:gc]` tag definitions
|
|
115
|
+
- `vocabulary/invocations.yml` — `memory_read_observation` primitive
|
|
116
|
+
- `conformance/state-schemas/memory-access.schema.json` — access log schema (canonical field names and types)
|
|
117
|
+
- `docs/nexus-outputs-contract.md §Shared filename convention` — `memory-access.jsonl` registration and location convention
|
|
118
|
+
- `docs/behavioral-contracts.md` — other behavioral contracts in nexus-core
|
|
119
|
+
- `.nexus/context/boundaries.md` — why specific thresholds are not canonical (거절 근거 및 Authoring layer 정체성)
|
package/docs/nexus-layout.md
CHANGED
|
@@ -12,13 +12,15 @@ This document is the canonical reference for the `.nexus/` directory structure u
|
|
|
12
12
|
│ ├── plan.json
|
|
13
13
|
│ ├── tasks.json
|
|
14
14
|
│ ├── tool-log.jsonl
|
|
15
|
-
│ ├── edit-tracker.json
|
|
16
|
-
│ ├── reopen-tracker.json
|
|
17
15
|
│ ├── artifacts/
|
|
18
16
|
│ ├── claude-nexus/ ← harness-namespaced directory (example)
|
|
19
|
-
│ │
|
|
17
|
+
│ │ ├── agent-tracker.json
|
|
18
|
+
│ │ ├── edit-tracker.json
|
|
19
|
+
│ │ └── reopen-tracker.json
|
|
20
20
|
│ └── opencode-nexus/ ← harness-namespaced directory (example)
|
|
21
|
-
│
|
|
21
|
+
│ ├── agent-tracker.json
|
|
22
|
+
│ ├── edit-tracker.json
|
|
23
|
+
│ └── reopen-tracker.json
|
|
22
24
|
├── history.json ← project-scoped (git-tracked, append-only)
|
|
23
25
|
├── memory/ ← project-scoped (git-tracked)
|
|
24
26
|
│ └── *.md
|
|
@@ -103,7 +105,7 @@ This document is the canonical reference for the `.nexus/` directory structure u
|
|
|
103
105
|
|
|
104
106
|
---
|
|
105
107
|
|
|
106
|
-
#### `state/edit-tracker.json`
|
|
108
|
+
#### `state/{harness-id}/edit-tracker.json`
|
|
107
109
|
|
|
108
110
|
**Purpose.** Tracks which files have been edited in the current session. Used by the bounded resume tier to detect intervening edits before allowing agent reuse.
|
|
109
111
|
|
|
@@ -111,13 +113,13 @@ This document is the canonical reference for the `.nexus/` directory structure u
|
|
|
111
113
|
|
|
112
114
|
**Git tracking.** Ignored.
|
|
113
115
|
|
|
114
|
-
**Lifecycle.** Created on first file edit. Cleared at session end.
|
|
116
|
+
**Lifecycle.** Created on first file edit. Cleared at session end by the consumer harness session hook.
|
|
115
117
|
|
|
116
|
-
**Owner.**
|
|
118
|
+
**Owner.** Consumer harness session hook. Not managed by any nexus-core MCP tool.
|
|
117
119
|
|
|
118
120
|
---
|
|
119
121
|
|
|
120
|
-
#### `state/reopen-tracker.json`
|
|
122
|
+
#### `state/{harness-id}/reopen-tracker.json`
|
|
121
123
|
|
|
122
124
|
**Purpose.** Records tasks or plan issues that have been reopened within the current cycle, to prevent infinite reopen loops.
|
|
123
125
|
|
|
@@ -125,9 +127,9 @@ This document is the canonical reference for the `.nexus/` directory structure u
|
|
|
125
127
|
|
|
126
128
|
**Git tracking.** Ignored.
|
|
127
129
|
|
|
128
|
-
**Lifecycle.** Created on first reopen event. Cleared at cycle end.
|
|
130
|
+
**Lifecycle.** Created on first reopen event. Cleared at cycle end by the consumer harness session hook.
|
|
129
131
|
|
|
130
|
-
**Owner.**
|
|
132
|
+
**Owner.** Consumer harness session hook. Not managed by any nexus-core MCP tool.
|
|
131
133
|
|
|
132
134
|
---
|
|
133
135
|
|
|
@@ -101,8 +101,8 @@ Nexus 산출물은 생성 책임 주체에 따라 세 카테고리로 분류된
|
|
|
101
101
|
**Schema reference**: `conformance/state-schemas/agent-tracker.schema.json`
|
|
102
102
|
|
|
103
103
|
**Interop requirement**:
|
|
104
|
-
- 하네스는 `agent-tracker.json`을 기록할 때 MUST `conformance/state-schemas/agent-tracker.schema.json`에 유효한 JSON을 기록해야 한다.
|
|
105
|
-
- MUST required 2 필드(`harness_id`, `started_at`)를 포함해야 한다.
|
|
104
|
+
- 하네스는 `agent-tracker.json`을 기록할 때 MUST `conformance/state-schemas/agent-tracker.schema.json`에 유효한 JSON을 기록해야 한다. 파일의 top-level 구조는 배열(`[]`)이며 각 원소가 하나의 에이전트 인스턴스 entry이다.
|
|
105
|
+
- 각 entry는 MUST required 2 필드(`harness_id`, `started_at`)를 포함해야 한다.
|
|
106
106
|
- SHOULD cross-harness display/liveness를 위한 추가 필드(`agent_name`, `status`)를 제공한다.
|
|
107
107
|
- `owner_agent_id`는 MUST harness-scoped opaque string으로 취급되어야 한다 — 다른 하네스의 `agent_id`를 parse하거나 추론하는 것은 MUST NOT 허용되어서는 안 된다. `agent_id`는 하네스별 내부 식별자이며 cross-harness 해석 대상이 아니다.
|
|
108
108
|
- `agent-tracker.json`은 MUST NOT git-tracked 상태로 commit되어서는 안 된다.
|
|
@@ -113,6 +113,33 @@ Nexus 산출물은 생성 책임 주체에 따라 세 카테고리로 분류된
|
|
|
113
113
|
|
|
114
114
|
---
|
|
115
115
|
|
|
116
|
+
### `memory-access.jsonl` — memory 파일 접근 기록
|
|
117
|
+
|
|
118
|
+
**경로**: `.nexus/state/{harness_id}/memory-access.jsonl`
|
|
119
|
+
|
|
120
|
+
**책임 주체**: 각 consumer 하네스의 memory-read observation hook. 어떤 MCP tool도 이 파일에 직접 write해서는 안 된다.
|
|
121
|
+
|
|
122
|
+
**생성 trigger**: 하네스가 `.nexus/memory/` 하위 파일의 읽기 이벤트를 관측할 때 MUST path-upsert 방식으로 기록해야 한다. 파일이 없으면 첫 관측 시 MUST 생성해야 한다.
|
|
123
|
+
|
|
124
|
+
**삭제 trigger**: 하네스 `[m:gc]` 핸들러가 gc 정책(merge/delete)을 실행할 때 관리한다. gc 주기 및 보존 정책은 consumer 하네스가 결정한다.
|
|
125
|
+
|
|
126
|
+
**Schema reference**: `conformance/state-schemas/memory-access.schema.json` (JSONL 형식 — 각 줄이 하나의 line-object이며 schema를 만족해야 한다. upsert key는 `path`).
|
|
127
|
+
|
|
128
|
+
**Interop requirement**:
|
|
129
|
+
- 하네스는 `memory-access.jsonl`을 기록할 때 MUST 각 JSONL line이 `conformance/state-schemas/memory-access.schema.json`에 유효한 객체여야 한다.
|
|
130
|
+
- upsert key `path`는 MUST `.nexus/memory/` 하위 파일의 경로를 담아야 한다.
|
|
131
|
+
- `last_accessed_ts` 필드는 MUST ISO 8601 형식을 사용해야 한다.
|
|
132
|
+
- `last_agent` 필드는 MUST harness-scoped opaque string으로 취급해야 한다 — 다른 하네스가 생성한 값을 parse하거나 구조를 추론하는 것은 MUST NOT 허용되어서는 안 된다.
|
|
133
|
+
- 두 consumer가 동일 schema로 기록하면 향후 공동 사용 프로젝트에서 access log를 병합할 수 있다. 병합 시 `path`를 동등 키로 사용하며 `access_count`는 합산한다.
|
|
134
|
+
- `memory-access.jsonl`은 MUST NOT git-tracked 상태로 commit되어서는 안 된다.
|
|
135
|
+
- 관련 vocabulary: `vocabulary/memory_policy.yml`, `vocabulary/invocations.yml`의 `memory_read_observation` primitive, `vocabulary/tags.yml`의 [m]/[m:gc] entries.
|
|
136
|
+
|
|
137
|
+
**Conformance fixture reference**: 해당 없음. `memory-access.jsonl`은 MCP tool behavioral fixture 범위 외에 있으며 하네스 memory-read observation hook이 전적으로 책임진다.
|
|
138
|
+
|
|
139
|
+
§Shared filename convention 섹션도 참조하라.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
116
143
|
## Agent-produced 산출물 (ephemeral)
|
|
117
144
|
|
|
118
145
|
### `artifacts/` 디렉토리 — 에이전트 생성 파일
|
|
@@ -195,7 +222,6 @@ Nexus 산출물은 생성 책임 주체에 따라 세 카테고리로 분류된
|
|
|
195
222
|
- **MUST NOT**: 다른 하네스의 namespace 디렉토리에 쓰거나 읽는다.
|
|
196
223
|
- **MUST NOT**: `{harness-id}/` 하위에서 공통 schema 파일명(plan.json, tasks.json, history.json)을 재사용한다. 예: `.nexus/state/claude-nexus/plan.json` 금지.
|
|
197
224
|
- **허용**: `.nexus/state/claude-nexus/agent-tracker.json` ✓ — shared-purpose file이며 §Shared filename convention에 등록된 첫 사례. 공통 schema 파일명 재사용 금지 규칙의 예외가 아니라, shared-purpose 유형으로 별도 분류된다.
|
|
198
|
-
- **예외**: v0.3.x 이하에 루트 경로로 등록된 legacy 2종(`edit-tracker.json`, `reopen-tracker.json`)은 `task_close` tool 계약에 묶여 있어 backward-compat으로 루트 유지 허용한다. 신규 파일에는 예외 편승 금지.
|
|
199
225
|
|
|
200
226
|
### Archive 정책
|
|
201
227
|
|
|
@@ -217,19 +243,19 @@ Nexus 산출물은 생성 책임 주체에 따라 세 카테고리로 분류된
|
|
|
217
243
|
├── artifacts/
|
|
218
244
|
├── claude-nexus/ ← namespace 디렉토리
|
|
219
245
|
│ ├── agent-tracker.json ← shared-purpose file (§Shared filename convention)
|
|
246
|
+
│ ├── memory-access.jsonl ← shared-purpose file (§Shared filename convention)
|
|
220
247
|
│ ├── plan.extension.json ← plan.json의 확장 (priority, estimated_effort 등)
|
|
221
248
|
│ ├── tasks.extension.json
|
|
222
249
|
│ ├── history.extension.json ← 하네스 자체 archive
|
|
223
|
-
│ ├── edit-tracker.json ← 독립 파일
|
|
224
|
-
│ ├── reopen-tracker.json ← 독립 파일
|
|
250
|
+
│ ├── edit-tracker.json ← 하네스 독립 파일
|
|
251
|
+
│ ├── reopen-tracker.json ← 하네스 독립 파일
|
|
225
252
|
│ └── tool-log.jsonl
|
|
226
253
|
└── opencode-nexus/ ← sibling 하네스 namespace 디렉토리
|
|
227
254
|
├── agent-tracker.json ← shared-purpose file (동일 파일명, harness-local)
|
|
255
|
+
├── memory-access.jsonl ← shared-purpose file (동일 파일명, harness-local)
|
|
228
256
|
└── ...
|
|
229
257
|
```
|
|
230
258
|
|
|
231
|
-
(루트 레벨 `edit-tracker.json`/`reopen-tracker.json`은 legacy carve-out으로 v0.3.x 이하 호환 유지)
|
|
232
|
-
|
|
233
259
|
### Reference example
|
|
234
260
|
|
|
235
261
|
`conformance/examples/plan.extension.schema.example.json`에 non-normative 참조 예시가 있다. 이 파일은 다음 요소를 보여준다:
|
|
@@ -271,6 +297,7 @@ nexus-core는 shared-purpose file에 대해 **최소 공통 schema contract**를
|
|
|
271
297
|
| 파일명 | schema | 목적 | 최초 적용 |
|
|
272
298
|
|---|---|---|---|
|
|
273
299
|
| `agent-tracker.json` | `conformance/state-schemas/agent-tracker.schema.json` | 에이전트 인스턴스 lifecycle tracking | v0.7.0 |
|
|
300
|
+
| `memory-access.jsonl` | `conformance/state-schemas/memory-access.schema.json` | memory 파일 읽기 이벤트 누적 기록 — informed gc 판단의 신호 기반 | v0.10.0 |
|
|
274
301
|
|
|
275
302
|
향후 신규 shared-purpose file을 추가할 때는 반드시 이 섹션에 등록해야 한다. 등록 없이 여러 하네스가 동일 파일명을 독립적으로 사용하는 것은 MUST NOT 허용되어서는 안 된다.
|
|
276
303
|
|
|
@@ -102,7 +102,7 @@ The Nexus state layout is divided into two categories:
|
|
|
102
102
|
|
|
103
103
|
**Purpose.** Tracks agent instance activity during the session — which agents were spawned, their instance IDs, and what artifacts they touched. Used by the harness to evaluate `owner_reuse_policy` on subsequent `task_add` calls and to support agent resume. Each harness writes its own file under a harness-specific subdirectory, keeping records isolated across harness namespaces.
|
|
104
104
|
|
|
105
|
-
**Schema.** The file contains a JSON object. Required fields: `harness_id` (string, identifies the writing harness) and `started_at` (ISO 8601 timestamp
|
|
105
|
+
**Schema.** The file contains a JSON array. Each entry is an object representing one spawned agent instance. Required fields per entry: `harness_id` (string, identifies the writing harness) and `started_at` (ISO 8601 timestamp when the agent instance was first started). The following fields are optional per entry: `agent_name`, `agent_id`, `last_resumed_at`, `resume_count`, `status`, `stopped_at`, `last_message`, and `files_touched`. Harness-defined extension fields are not permitted — `additionalProperties` is false per the schema.
|
|
106
106
|
|
|
107
107
|
The `agent_id` field, when present, is a harness-specific opaque agent instance identifier. Its format is defined by the writing harness (for example, a UUID, a composite string, or another harness-native scheme). Consumers of this file treat the value as opaque — they do not parse it or infer agent identity across harness namespaces.
|
|
108
108
|
|
|
@@ -326,13 +326,14 @@ None.
|
|
|
326
326
|
| `total_cycles` | `number` | Total number of cycles in `history.json` after closure |
|
|
327
327
|
| `memoryHint.taskCount` | `number` | Number of tasks in the closed cycle |
|
|
328
328
|
| `memoryHint.decisionCount` | `number` | Number of decided issues in the closed cycle |
|
|
329
|
-
| `memoryHint.hadLoopDetection` | `boolean` | `true` if the edit tracker recorded three or more edits to any single file during the cycle |
|
|
330
329
|
| `memoryHint.cycleTopics` | `string[]` | Non-empty strings from `plan.topic` and `tasks.goal` |
|
|
331
330
|
|
|
332
331
|
### Side Effects
|
|
333
332
|
|
|
334
333
|
- Appends a cycle record to `.nexus/history.json` (creating the file if absent). The record contains `completed_at`, `branch`, `plan` (full `PlanFile` or `null`), and `tasks` (full `Task[]`).
|
|
335
|
-
- Deletes the following session-scoped files if they exist: `plan.json`, `tasks.json
|
|
334
|
+
- Deletes the following session-scoped files if they exist: `plan.json`, `tasks.json` (all within `.nexus/state/`).
|
|
335
|
+
|
|
336
|
+
Harness-local tracker files (`edit-tracker.json`, `reopen-tracker.json`, and any other files under `.nexus/state/{harness-id}/`) are not managed by `task_close`. Their lifecycle is the responsibility of consumer harness session hooks.
|
|
336
337
|
|
|
337
338
|
### Error Conditions
|
|
338
339
|
|
package/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"nexus_core_version": "0.
|
|
3
|
-
"nexus_core_commit": "
|
|
2
|
+
"nexus_core_version": "0.10.0",
|
|
3
|
+
"nexus_core_commit": "23c72e53a32308b015eb90468dee3cb6e80eb655",
|
|
4
4
|
"schema_contract_version": "2.0",
|
|
5
5
|
"agents": [
|
|
6
6
|
{
|
|
@@ -20,19 +20,34 @@
|
|
|
20
20
|
"body_hash": "sha256:85f9a3de419f32cdae284436eb1d902bff19a2230c50fe3068ffc642949a63b7"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
|
-
"name": "
|
|
24
|
-
"description": "
|
|
25
|
-
"task": "
|
|
26
|
-
"alias_ko": "
|
|
27
|
-
"category": "
|
|
28
|
-
"resume_tier": "
|
|
23
|
+
"name": "engineer",
|
|
24
|
+
"description": "Implementation — writes code, debugs issues, follows specifications from Lead and architect",
|
|
25
|
+
"task": "Code implementation, edits, debugging",
|
|
26
|
+
"alias_ko": "엔지니어",
|
|
27
|
+
"category": "do",
|
|
28
|
+
"resume_tier": "bounded",
|
|
29
29
|
"model_tier": "standard",
|
|
30
30
|
"capabilities": [
|
|
31
|
-
"no_file_edit",
|
|
32
31
|
"no_task_create"
|
|
33
32
|
],
|
|
34
|
-
"id": "
|
|
35
|
-
"body_hash": "sha256:
|
|
33
|
+
"id": "engineer",
|
|
34
|
+
"body_hash": "sha256:3d58b1b490c2f93cace2eedd0f04ec000f84514388eb086768cf53f8fa33db01"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "designer",
|
|
38
|
+
"description": "UX/UI design — evaluates user experience, interaction patterns, and how users will experience the product",
|
|
39
|
+
"task": "UI/UX design, interaction patterns, user experience",
|
|
40
|
+
"alias_ko": "디자이너",
|
|
41
|
+
"category": "how",
|
|
42
|
+
"resume_tier": "persistent",
|
|
43
|
+
"model_tier": "high",
|
|
44
|
+
"capabilities": [
|
|
45
|
+
"no_file_edit",
|
|
46
|
+
"no_task_create",
|
|
47
|
+
"no_task_update"
|
|
48
|
+
],
|
|
49
|
+
"id": "designer",
|
|
50
|
+
"body_hash": "sha256:88ac56147d0e5bdf23fa591ce570a9c2d0eb1338df4ec2219f6238ddfcb65df4"
|
|
36
51
|
},
|
|
37
52
|
{
|
|
38
53
|
"name": "strategist",
|
|
@@ -51,20 +66,19 @@
|
|
|
51
66
|
"body_hash": "sha256:0254b4144a22c66209bd68119553d9057a4fb7f9b1ff7ebb9878687d99583465"
|
|
52
67
|
},
|
|
53
68
|
{
|
|
54
|
-
"name": "
|
|
55
|
-
"description": "
|
|
56
|
-
"task": "
|
|
57
|
-
"alias_ko": "
|
|
58
|
-
"category": "
|
|
59
|
-
"resume_tier": "
|
|
60
|
-
"model_tier": "
|
|
69
|
+
"name": "reviewer",
|
|
70
|
+
"description": "Content verification — validates accuracy, checks facts, confirms grammar and format of non-code deliverables",
|
|
71
|
+
"task": "Content verification, fact-checking, grammar review",
|
|
72
|
+
"alias_ko": "리뷰어",
|
|
73
|
+
"category": "check",
|
|
74
|
+
"resume_tier": "ephemeral",
|
|
75
|
+
"model_tier": "standard",
|
|
61
76
|
"capabilities": [
|
|
62
77
|
"no_file_edit",
|
|
63
|
-
"no_task_create"
|
|
64
|
-
"no_task_update"
|
|
78
|
+
"no_task_create"
|
|
65
79
|
],
|
|
66
|
-
"id": "
|
|
67
|
-
"body_hash": "sha256:
|
|
80
|
+
"id": "reviewer",
|
|
81
|
+
"body_hash": "sha256:f04d15249601b14046e7e40a4475defb289436c4474afbd89986964f8c3e7c2f"
|
|
68
82
|
},
|
|
69
83
|
{
|
|
70
84
|
"name": "researcher",
|
|
@@ -81,20 +95,6 @@
|
|
|
81
95
|
"id": "researcher",
|
|
82
96
|
"body_hash": "sha256:fc79bafec05503327bd51a0b84b6e642d304bd79c45b78db6448b112793c143e"
|
|
83
97
|
},
|
|
84
|
-
{
|
|
85
|
-
"name": "engineer",
|
|
86
|
-
"description": "Implementation — writes code, debugs issues, follows specifications from Lead and architect",
|
|
87
|
-
"task": "Code implementation, edits, debugging",
|
|
88
|
-
"alias_ko": "엔지니어",
|
|
89
|
-
"category": "do",
|
|
90
|
-
"resume_tier": "bounded",
|
|
91
|
-
"model_tier": "standard",
|
|
92
|
-
"capabilities": [
|
|
93
|
-
"no_task_create"
|
|
94
|
-
],
|
|
95
|
-
"id": "engineer",
|
|
96
|
-
"body_hash": "sha256:3d58b1b490c2f93cace2eedd0f04ec000f84514388eb086768cf53f8fa33db01"
|
|
97
|
-
},
|
|
98
98
|
{
|
|
99
99
|
"name": "postdoc",
|
|
100
100
|
"description": "Research methodology and synthesis — designs investigation approach, evaluates evidence quality, writes synthesis documents",
|
|
@@ -142,6 +142,19 @@
|
|
|
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
|
+
},
|
|
145
158
|
{
|
|
146
159
|
"name": "nx-init",
|
|
147
160
|
"description": "Project onboarding — scan, mission, essentials, context generation",
|
|
@@ -175,20 +188,7 @@
|
|
|
175
188
|
"resume_invocation"
|
|
176
189
|
],
|
|
177
190
|
"id": "nx-plan",
|
|
178
|
-
"body_hash": "sha256:
|
|
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
|
-
],
|
|
190
|
-
"id": "nx-run",
|
|
191
|
-
"body_hash": "sha256:0e2c443efceeab4621709a85cd4e2ba50471d2e850680c655d776cbb62814549"
|
|
191
|
+
"body_hash": "sha256:083ce49c06f8d3e4a0299aa8eb8e33460b68d6a277fe356b4db9635c21016aff"
|
|
192
192
|
}
|
|
193
193
|
],
|
|
194
194
|
"vocabulary": {
|
|
@@ -302,14 +302,16 @@
|
|
|
302
302
|
"trigger": "[m]",
|
|
303
303
|
"type": "inline_action",
|
|
304
304
|
"handler": "memory_store",
|
|
305
|
-
"description": "Stores a lesson or reference to .nexus/memory/"
|
|
305
|
+
"description": "Stores a lesson or reference to .nexus/memory/",
|
|
306
|
+
"prose_guidance": "저장 admission 기준 — 코드/웹에서 다시 얻을 수 없는 정보만 저장한다. memory 파일의 naming, category, lifecycle 운영 정책은 vocabulary/memory_policy.yml과 docs/memory-lifecycle-contract.md를 canonical source로 참조한다.\n"
|
|
306
307
|
},
|
|
307
308
|
{
|
|
308
309
|
"id": "m-gc",
|
|
309
310
|
"trigger": "[m:gc]",
|
|
310
311
|
"type": "inline_action",
|
|
311
312
|
"handler": "memory_gc",
|
|
312
|
-
"description": "Garbage-collects .nexus/memory/ by merging or removing stale entries"
|
|
313
|
+
"description": "Garbage-collects .nexus/memory/ by merging or removing stale entries",
|
|
314
|
+
"prose_guidance": "gc 트리거 조건 평가, merge 판단, forgetting policy 집행은 vocabulary/memory_policy.yml에 정의된 원칙을 따르고 구체 임계값은 consumer-local 설정으로 결정한다.\n"
|
|
313
315
|
},
|
|
314
316
|
{
|
|
315
317
|
"id": "rule",
|
|
@@ -346,7 +348,101 @@
|
|
|
346
348
|
"description": "Ask the user a structured question with selectable options.",
|
|
347
349
|
"intent": "structured_user_prompt",
|
|
348
350
|
"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"
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
"id": "memory_read_observation",
|
|
354
|
+
"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",
|
|
355
|
+
"intent": "memory_read_observation",
|
|
356
|
+
"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"
|
|
357
|
+
}
|
|
358
|
+
],
|
|
359
|
+
"task_exceptions": [
|
|
360
|
+
{
|
|
361
|
+
"id": "docs_only.coherent",
|
|
362
|
+
"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.",
|
|
363
|
+
"applies_when": "all changed files are .md or frontmatter-only, and share one common scheme, decision, or structural change",
|
|
364
|
+
"treatment": "bundle into 1 writer task + 1 reviewer pair; file count / line count thresholds waived; state coherence claim in task approach field",
|
|
365
|
+
"rationale": "e.g., updating agent frontmatter files after a new field is introduced; files share the same change intent"
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
"id": "docs_only.independent",
|
|
369
|
+
"description": "A set of documentation files where each file addresses a distinct topic with no cross-reference dependency.",
|
|
370
|
+
"applies_when": "each .md file addresses a distinct topic with no cross-reference dependency",
|
|
371
|
+
"treatment": "N parallel writer tasks (one per file), each paired with 1 reviewer task; file count threshold waived per-task",
|
|
372
|
+
"rationale": "e.g., separate proposal drafts; each file is independently consumed"
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
"id": "same_file_bundle",
|
|
376
|
+
"description": "Two or more decomposed sub-tasks that would each modify the same target file, requiring merger to avoid conflicts.",
|
|
377
|
+
"applies_when": "two or more sub-tasks in the decomposition would each modify the same target file",
|
|
378
|
+
"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",
|
|
379
|
+
"rationale": "parallel subagents targeting the same file cause merge conflicts"
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"id": "generated_artifacts",
|
|
383
|
+
"description": "Files that are build output and do not represent independent authoring decisions.",
|
|
384
|
+
"applies_when": "files are build output (e.g., paths declared as build outputs in the harness's build configuration)",
|
|
385
|
+
"treatment": "excluded from task count, artifact cluster file count, and line count calculations; committed as part of the task that triggers their generation",
|
|
386
|
+
"rationale": "generated files do not represent independent authoring decisions"
|
|
387
|
+
}
|
|
388
|
+
],
|
|
389
|
+
"memory_policy": {
|
|
390
|
+
"categories": [
|
|
391
|
+
{
|
|
392
|
+
"id": "empirical",
|
|
393
|
+
"prefix": "empirical-",
|
|
394
|
+
"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"
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
"id": "external",
|
|
398
|
+
"prefix": "external-",
|
|
399
|
+
"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"
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
"id": "pattern",
|
|
403
|
+
"prefix": "pattern-",
|
|
404
|
+
"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"
|
|
405
|
+
}
|
|
406
|
+
],
|
|
407
|
+
"naming": {
|
|
408
|
+
"pattern": "^[a-z0-9][a-z0-9-]*\\.md$",
|
|
409
|
+
"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",
|
|
410
|
+
"optional_prefix": true
|
|
411
|
+
},
|
|
412
|
+
"access_tracking": {
|
|
413
|
+
"observation_primitive": "file_read",
|
|
414
|
+
"scope": ".nexus/memory/",
|
|
415
|
+
"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",
|
|
416
|
+
"information_accumulated": [
|
|
417
|
+
{
|
|
418
|
+
"name": "last_access_timestamp",
|
|
419
|
+
"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"
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
"name": "access_count",
|
|
423
|
+
"meaning": "Cumulative count of read events observed for this file since tracking began.\n"
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
"name": "last_reader_identity",
|
|
427
|
+
"meaning": "Identifier of the most recent reader (agent id or equivalent). Harness-local value domain.\n"
|
|
428
|
+
}
|
|
429
|
+
],
|
|
430
|
+
"storage_contract_reference": "conformance/state-schemas/memory-access.schema.json"
|
|
431
|
+
},
|
|
432
|
+
"forgetting": {
|
|
433
|
+
"manual_gate_default": true,
|
|
434
|
+
"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",
|
|
435
|
+
"automatic_deletion_structure": {
|
|
436
|
+
"principle": "minimum_three_signal_intersection",
|
|
437
|
+
"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"
|
|
438
|
+
},
|
|
439
|
+
"recoverable_deletion_requirement": "git_commit",
|
|
440
|
+
"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"
|
|
441
|
+
},
|
|
442
|
+
"merge": {
|
|
443
|
+
"principle": "merge_before_create",
|
|
444
|
+
"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"
|
|
349
445
|
}
|
|
350
|
-
|
|
446
|
+
}
|
|
351
447
|
}
|
|
352
448
|
}
|
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
|
|
@@ -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.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Canonical memory policy vocabulary for .nexus/memory/.
|
|
2
|
+
# Defines categories, naming conventions, access tracking semantics,
|
|
3
|
+
# forgetting policy, and merge preference.
|
|
4
|
+
# Consumers (claude-nexus, opencode-nexus) read this file; specific thresholds
|
|
5
|
+
# and enforcement mechanics are consumer-local.
|
|
6
|
+
|
|
7
|
+
categories:
|
|
8
|
+
- id: empirical
|
|
9
|
+
prefix: empirical-
|
|
10
|
+
description: >
|
|
11
|
+
Empirically verified findings — observations and measurements that the
|
|
12
|
+
project has confirmed through its own experimentation. Examples include
|
|
13
|
+
runtime behavior observations, testing-derived structural facts, and
|
|
14
|
+
operational measurements that cannot be inferred from documentation alone.
|
|
15
|
+
- id: external
|
|
16
|
+
prefix: external-
|
|
17
|
+
description: >
|
|
18
|
+
External constraints and references — requirements imposed by upstream
|
|
19
|
+
dependencies, third-party API limits, vendor documentation quotations, and
|
|
20
|
+
any knowledge that originates outside the project. May become stale if
|
|
21
|
+
the upstream source changes.
|
|
22
|
+
- id: pattern
|
|
23
|
+
prefix: pattern-
|
|
24
|
+
description: >
|
|
25
|
+
Tactical operational patterns — recurring cycle-level recipes, routing
|
|
26
|
+
heuristics, and procedural knowledge developed through work on the project.
|
|
27
|
+
Architectural or design-level patterns belong in .nexus/context/, not here.
|
|
28
|
+
|
|
29
|
+
naming:
|
|
30
|
+
pattern: "^[a-z0-9][a-z0-9-]*\\.md$"
|
|
31
|
+
description: >
|
|
32
|
+
Memory filenames are lowercase kebab-case .md files. The name should be a
|
|
33
|
+
descriptive topic of 2–4 words. An optional category prefix from the
|
|
34
|
+
categories section above may precede the topic. Version numbers and dates
|
|
35
|
+
must not appear in filenames — temporal information belongs inside the file.
|
|
36
|
+
optional_prefix: true
|
|
37
|
+
|
|
38
|
+
access_tracking:
|
|
39
|
+
observation_primitive: file_read
|
|
40
|
+
scope: .nexus/memory/
|
|
41
|
+
description: >
|
|
42
|
+
Harnesses observe the moment an agent reads a memory file. Save events,
|
|
43
|
+
directory scans (glob, grep), and mentions of the path in prose are not
|
|
44
|
+
observation events. The set of events observed determines the accumulated
|
|
45
|
+
access record.
|
|
46
|
+
information_accumulated:
|
|
47
|
+
- name: last_access_timestamp
|
|
48
|
+
meaning: >
|
|
49
|
+
Wall-clock time of the most recent read event. Field name is harness-local;
|
|
50
|
+
the canonical schema for storage is conformance/state-schemas/memory-access.schema.json.
|
|
51
|
+
- name: access_count
|
|
52
|
+
meaning: >
|
|
53
|
+
Cumulative count of read events observed for this file since tracking began.
|
|
54
|
+
- name: last_reader_identity
|
|
55
|
+
meaning: >
|
|
56
|
+
Identifier of the most recent reader (agent id or equivalent). Harness-local
|
|
57
|
+
value domain.
|
|
58
|
+
storage_contract_reference: conformance/state-schemas/memory-access.schema.json
|
|
59
|
+
|
|
60
|
+
forgetting:
|
|
61
|
+
manual_gate_default: true
|
|
62
|
+
description: >
|
|
63
|
+
Manual gc (triggered by the [m:gc] tag) is the default forgetting path.
|
|
64
|
+
Automatic deletion is opt-in per consumer and never runs without explicit
|
|
65
|
+
enablement.
|
|
66
|
+
automatic_deletion_structure:
|
|
67
|
+
principle: minimum_three_signal_intersection
|
|
68
|
+
description: >
|
|
69
|
+
If a consumer enables automatic deletion, the policy must require the
|
|
70
|
+
simultaneous satisfaction of at least three independent signals — for
|
|
71
|
+
example, elapsed time since last access, cycles since last read, and
|
|
72
|
+
cumulative access count. Single-signal automatic deletion is prohibited.
|
|
73
|
+
The specific signal thresholds are consumer-local and must be set to
|
|
74
|
+
match the project's cycle cadence.
|
|
75
|
+
recoverable_deletion_requirement: git_commit
|
|
76
|
+
recoverable_deletion_description: >
|
|
77
|
+
Every memory file deletion must be recorded as a git commit. The commit
|
|
78
|
+
message should include a recovery path that allows the file to be
|
|
79
|
+
reconstructed via git history. Specific commit message format is
|
|
80
|
+
consumer-local.
|
|
81
|
+
|
|
82
|
+
merge:
|
|
83
|
+
principle: merge_before_create
|
|
84
|
+
description: >
|
|
85
|
+
When a new memory save candidate substantively overlaps an existing file
|
|
86
|
+
in topic and category, merging the new content into the existing file is
|
|
87
|
+
preferred over creating a new file. Specific overlap-detection criteria
|
|
88
|
+
(for example, keyword match thresholds) are consumer-local.
|
package/vocabulary/tags.yml
CHANGED
|
@@ -36,12 +36,21 @@ tags:
|
|
|
36
36
|
type: inline_action
|
|
37
37
|
handler: memory_store
|
|
38
38
|
description: "Stores a lesson or reference to .nexus/memory/"
|
|
39
|
+
prose_guidance: >
|
|
40
|
+
저장 admission 기준 — 코드/웹에서 다시 얻을 수 없는 정보만 저장한다.
|
|
41
|
+
memory 파일의 naming, category, lifecycle 운영 정책은
|
|
42
|
+
vocabulary/memory_policy.yml과 docs/memory-lifecycle-contract.md를
|
|
43
|
+
canonical source로 참조한다.
|
|
39
44
|
|
|
40
45
|
- id: m-gc
|
|
41
46
|
trigger: "[m:gc]"
|
|
42
47
|
type: inline_action
|
|
43
48
|
handler: memory_gc
|
|
44
49
|
description: "Garbage-collects .nexus/memory/ by merging or removing stale entries"
|
|
50
|
+
prose_guidance: >
|
|
51
|
+
gc 트리거 조건 평가, merge 판단, forgetting policy 집행은
|
|
52
|
+
vocabulary/memory_policy.yml에 정의된 원칙을 따르고
|
|
53
|
+
구체 임계값은 consumer-local 설정으로 결정한다.
|
|
45
54
|
|
|
46
55
|
- id: rule
|
|
47
56
|
trigger: "[rule]"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Canonical exception rules for task decomposition and counting.
|
|
2
|
+
# Each entry defines a condition under which standard task decomposition rules
|
|
3
|
+
# are suspended or modified, along with the alternative treatment to apply.
|
|
4
|
+
# Consumers apply these exceptions during nx-plan task synthesis.
|
|
5
|
+
|
|
6
|
+
task_exceptions:
|
|
7
|
+
- id: docs_only.coherent
|
|
8
|
+
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."
|
|
9
|
+
applies_when: "all changed files are .md or frontmatter-only, and share one common scheme, decision, or structural change"
|
|
10
|
+
treatment: "bundle into 1 writer task + 1 reviewer pair; file count / line count thresholds waived; state coherence claim in task approach field"
|
|
11
|
+
rationale: "e.g., updating agent frontmatter files after a new field is introduced; files share the same change intent"
|
|
12
|
+
|
|
13
|
+
- id: docs_only.independent
|
|
14
|
+
description: "A set of documentation files where each file addresses a distinct topic with no cross-reference dependency."
|
|
15
|
+
applies_when: "each .md file addresses a distinct topic with no cross-reference dependency"
|
|
16
|
+
treatment: "N parallel writer tasks (one per file), each paired with 1 reviewer task; file count threshold waived per-task"
|
|
17
|
+
rationale: "e.g., separate proposal drafts; each file is independently consumed"
|
|
18
|
+
|
|
19
|
+
- id: same_file_bundle
|
|
20
|
+
description: "Two or more decomposed sub-tasks that would each modify the same target file, requiring merger to avoid conflicts."
|
|
21
|
+
applies_when: "two or more sub-tasks in the decomposition would each modify the same target file"
|
|
22
|
+
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"
|
|
23
|
+
rationale: "parallel subagents targeting the same file cause merge conflicts"
|
|
24
|
+
|
|
25
|
+
- id: generated_artifacts
|
|
26
|
+
description: "Files that are build output and do not represent independent authoring decisions."
|
|
27
|
+
applies_when: "files are build output (e.g., paths declared as build outputs in the harness's build configuration)"
|
|
28
|
+
treatment: "excluded from task count, artifact cluster file count, and line count calculations; committed as part of the task that triggers their generation"
|
|
29
|
+
rationale: "generated files do not represent independent authoring decisions"
|