@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 CHANGED
@@ -11,29 +11,30 @@ Nexus 생태계는 세 층위로 나뉩니다. `nexus-core`는 가장 아래, **
11
11
  ```
12
12
  Supervision (reserved)
13
13
  │ read-only
14
- Execution claude-nexus ↔ opencode-nexus
14
+ Execution claude-nexus ↔ opencode-nexus ↔ codex-nexus
15
15
  │ read-only
16
16
  Authoring nexus-core ← 이 저장소
17
17
  ```
18
18
 
19
- 현재 active 소비자는 Execution layer 하네스(`claude-nexus`, `opencode-nexus`)이며, 모두 `nexus-core`를 **read-only**로 참조합니다. Supervision layer는 외부 감독자 consumer를 위해 예약된 자리입니다(과거 nexus-code 프로젝트가 이 layer를 구현했으나 2026-04-14 archived).
19
+ 현재 active 소비자는 Execution layer 하네스(`claude-nexus`, `opencode-nexus`, `codex-nexus`)이며, 모두 `nexus-core`를 **read-only**로 참조합니다. Supervision layer는 외부 감독자 consumer를 위해 예약된 자리입니다(과거 nexus-code 프로젝트가 이 layer를 구현했으나 2026-04-14 archived).
20
20
 
21
21
  | Consumer | Layer | 하는 일 |
22
22
  |---|---|---|
23
23
  | [`claude-nexus`](https://github.com/moreih29/claude-nexus) | Execution | Claude Code 하네스 위에서 에이전트 조립·디스패치 |
24
24
  | [`opencode-nexus`](https://github.com/moreih29/opencode-nexus) | Execution | OpenCode 하네스 위에서 에이전트 조립·디스패치 |
25
+ | [`codex-nexus`](https://github.com/moreih29/codex-nexus) | Execution | Codex 하네스 위에서 에이전트 조립·디스패치 |
25
26
 
26
27
  ## For Consumer Repositories
27
28
 
28
- > 이 저장소는 **외부 사용자가 직접 설치하는 플러그인이 아닙니다**. Nexus 하네스(`claude-nexus`, `opencode-nexus`)를 사용하려면 해당 저장소의 안내를 따르세요.
29
+ > 이 저장소는 **외부 사용자가 직접 설치하는 플러그인이 아닙니다**. Nexus 하네스(`claude-nexus`, `opencode-nexus`, `codex-nexus`)를 사용하려면 해당 저장소의 안내를 따르세요.
29
30
 
30
- Consumer 저장소(`claude-nexus`, `opencode-nexus`)의 LLM 에이전트가 `@moreih29/nexus-core` 버전 업그레이드를 처리해야 하는 경우, **[CONSUMING.md](./CONSUMING.md)**의 Upgrade Protocol을 참조하세요.
31
+ Consumer 저장소(`claude-nexus`, `opencode-nexus`, `codex-nexus`)의 LLM 에이전트가 `@moreih29/nexus-core` 버전 업그레이드를 처리해야 하는 경우, **[CONSUMING.md](./CONSUMING.md)**의 Upgrade Protocol을 참조하세요.
31
32
 
32
33
  CONSUMING.md는 LLM 에이전트 전용 문서입니다. 사람 독자는 이 README가 더 유용합니다.
33
34
 
34
35
  ## 이 저장소는 무엇이 **아닌가**
35
36
 
36
- `nexus-core`는 **외부 사용자가 직접 설치하는 플러그인이 아닙니다.** Nexus 하네스(`claude-nexus`, `opencode-nexus`)를 사용하고 싶다면 해당 저장소의 안내를 따르세요. `nexus-core`는 그 하네스가 내부적으로 공유하는 자산입니다.
37
+ `nexus-core`는 **외부 사용자가 직접 설치하는 플러그인이 아닙니다.** Nexus 하네스(`claude-nexus`, `opencode-nexus`, `codex-nexus`)를 사용하고 싶다면 해당 저장소의 안내를 따르세요. `nexus-core`는 그 하네스가 내부적으로 공유하는 자산입니다.
37
38
 
38
39
  ## 범위
39
40
 
@@ -69,7 +70,7 @@ CONSUMING.md는 LLM 에이전트 전용 문서입니다. 사람 독자는 이 RE
69
70
 
70
71
  ## Status
71
72
 
72
- v0.7.1 (2026-04-14). 최신 release: agent-tracker namespace isolation (v0.7.0, GH #16) + nexus-code archived cleanup (v0.7.1). 상세 변경 이력은 [CHANGELOG.md](./CHANGELOG.md) 참조.
73
+ v0.11.0 (2026-04-17). 최신 release: 컨텍스트 주입 가이드 governance 재정립 (GH #21/#22/#23 3 consumer drift 대응). 상세 변경 이력은 [CHANGELOG.md](./CHANGELOG.md) 참조.
73
74
 
74
75
  ## References
75
76
 
@@ -9,6 +9,11 @@
9
9
  | `agent-spawn.json` | `agent_spawn` | `agent-tracker.json` 첫 항목 생성 (running 상태) |
10
10
  | `agent-complete.json` | `agent_complete` | `agent-tracker.json` 항목 완료 상태 전환 |
11
11
  | `agent-resume.json` | `agent_resume` | `agent-tracker.json` 재개 카운터 및 상태 복귀 |
12
+ | `session-end.json` | `session_end` | `agent-tracker.json` 삭제, `history.json` · `memory/` · `context/` · `rules/` 보존 |
13
+
14
+ ## Why session-end was re-added at v0.11.0
15
+
16
+ v0.6.0에서 session-start/session-end fixture를 제거한 이유는 당시 구현이 `runtime.schema.json`에 의존했기 때문이다. `runtime.schema.json`은 execution semantics를 포함하므로 nexus-core의 prompt-only 원칙과 충돌했다. v0.11.0 재도입은 `runtime.schema.json`을 완전히 배제하고 `agent-tracker.schema.json`과 `history.schema.json`만 참조한다. session_end fixture가 검증하는 핵심 invariant는 두 가지다: (1) agent-tracker.json은 세션 종료 시 삭제된다(session-scoped), (2) history.json · memory/ · context/ · rules/는 삭제되어서는 안 된다(Negative MUST — cross-session knowledge 보존).
12
17
 
13
18
  ## Tool-action 대신 Event 트리거 사용
14
19
 
@@ -31,7 +36,7 @@ Test runner는 이 키를 파일 시스템 경로로 해석하기 전에 다음
31
36
 
32
37
  치환 규약:
33
38
  - `{STATE_ROOT}`와 `{HARNESS_ID}` 두 token만 인식된다. 그 외 `{…}` 형태의 token이 경로에 등장하면 authoring 오류로 처리한다.
34
- - 공통 파일 fixture (`plan-*`, `task-*`, `history-*`, `artifact-write`)는 하드코딩된 경로를 사용하며 이 token 규약이 적용되지 않는다. Token 경로는 lifecycle fixture 3종에만 적용된다.
39
+ - 공통 파일 fixture (`plan-*`, `task-*`, `history-*`, `artifact-write`)는 하드코딩된 경로를 사용하며 이 token 규약이 적용되지 않는다. Token 경로는 lifecycle fixture 4종(`agent-spawn`, `agent-complete`, `agent-resume`, `session-end`)의 `agent-tracker.json` 경로에만 적용된다.
35
40
 
36
41
  ### Fixture별 경로 요약
37
42
 
@@ -40,3 +45,4 @@ Test runner는 이 키를 파일 시스템 경로로 해석하기 전에 다음
40
45
  | `agent-spawn.json` | `claude-nexus` | `.nexus/state/claude-nexus/agent-tracker.json` |
41
46
  | `agent-complete.json` | `claude-nexus` | `.nexus/state/claude-nexus/agent-tracker.json` |
42
47
  | `agent-resume.json` | `claude-nexus` | `.nexus/state/claude-nexus/agent-tracker.json` |
48
+ | `session-end.json` | `claude-nexus` | `.nexus/state/claude-nexus/agent-tracker.json` (null assertion — file must not exist after session end) |
@@ -0,0 +1,27 @@
1
+ {
2
+ "test_id": "memory_access_record",
3
+ "description": "Verifies that a memory-access.jsonl record is written with the required fields when an agent reads a memory file during a session",
4
+ "precondition": {
5
+ "state_files": {
6
+ "{STATE_ROOT}/{HARNESS_ID}/memory-access.jsonl": null
7
+ }
8
+ },
9
+ "event": {
10
+ "type": "session_end",
11
+ "params": {
12
+ "harness_id": "claude-nexus"
13
+ },
14
+ "description": "Harness reads memory file and appends an access record to memory-access.jsonl. This fixture validates the structure of the resulting JSONL record against memory-access.schema.json."
15
+ },
16
+ "postcondition": {
17
+ "state_files": {
18
+ "{STATE_ROOT}/{HARNESS_ID}/memory-access.jsonl": {}
19
+ }
20
+ },
21
+ "covers": {
22
+ "state_schemas": {
23
+ "memory-access.schema.json": ["path", "last_accessed_ts", "access_count", "last_agent", "schema_version"]
24
+ },
25
+ "description": "memory-access.jsonl is a JSONL file where each line is a memory-access.schema.json record. This fixture asserts the file exists after a session where memory was read. Content is not inspected via JSONPath because JSONL is not parseable as a single JSON object; field coverage is declared here to satisfy the schema coverage gate."
26
+ }
27
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "test_id": "session_end",
3
+ "description": "Verifies that session_end deletes agent-tracker.json while preserving history.json and the memory/context/rules knowledge directories",
4
+ "precondition": {
5
+ "state_files": {
6
+ "{STATE_ROOT}/{HARNESS_ID}/agent-tracker.json": [
7
+ {
8
+ "harness_id": "claude-nexus",
9
+ "agent_name": "architect",
10
+ "agent_id": "uuid-eng01",
11
+ "started_at": "2026-04-13T00:00:00.000Z",
12
+ "resume_count": 0,
13
+ "status": "completed",
14
+ "stopped_at": "2026-04-13T01:00:00.000Z",
15
+ "last_message": "Implementation complete",
16
+ "files_touched": ["src/foo.ts"]
17
+ }
18
+ ],
19
+ ".nexus/history.json": {},
20
+ ".nexus/memory/lessons.md": {},
21
+ ".nexus/context/architecture.md": {},
22
+ ".nexus/rules/project.md": {}
23
+ }
24
+ },
25
+ "event": {
26
+ "type": "session_end",
27
+ "params": {
28
+ "harness_id": "claude-nexus"
29
+ },
30
+ "description": "Harness teardown on session close: agent-tracker registry is deleted (session-scoped), while history, memory, context, and rules are retained across sessions"
31
+ },
32
+ "postcondition": {
33
+ "state_files": {
34
+ "{STATE_ROOT}/{HARNESS_ID}/agent-tracker.json": null,
35
+ ".nexus/history.json": {},
36
+ ".nexus/memory/lessons.md": {},
37
+ ".nexus/context/architecture.md": {},
38
+ ".nexus/rules/project.md": {}
39
+ }
40
+ },
41
+ "covers": {
42
+ "state_schemas": {
43
+ "agent-tracker.schema.json": ["harness_id", "agent_name", "agent_id", "started_at", "resume_count", "status", "stopped_at", "last_message", "files_touched[]"],
44
+ "history.schema.json": ["cycles"]
45
+ },
46
+ "description": "session_end fixture verifies the deletion boundary: agent-tracker.json (session-scoped) is deleted, history.json existence is asserted via empty-object check (file must exist, content not re-validated here). memory/context/rules files are verified present via empty-object check, confirming the do-not-delete MUST constraint. agent-tracker fields are listed here for completeness; leaf-field coverage is already established by agent-spawn/agent-complete/agent-resume fixtures."
47
+ }
48
+ }
@@ -166,8 +166,8 @@
166
166
  "properties": {
167
167
  "type": {
168
168
  "type": "string",
169
- "enum": ["agent_spawn", "agent_complete", "agent_resume"],
170
- "description": "Lifecycle event type. agent_spawn/agent_complete/agent_resume: agent instance lifecycle."
169
+ "enum": ["agent_spawn", "agent_complete", "agent_resume", "session_end"],
170
+ "description": "Lifecycle event type. agent_spawn/agent_complete/agent_resume: agent instance lifecycle. session_end: session teardown — agent-tracker.json deleted, history/memory/context/rules preserved."
171
171
  },
172
172
  "params": {
173
173
  "type": "object",
@@ -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
+ }
@@ -503,11 +503,15 @@ The canonical trigger for every tag is the explicit bracket form in `vocabulary/
503
503
 
504
504
  Hooks are the consumer's mechanism for responding to lifecycle events. nexus-core defines 8 abstract events. The names are harness-neutral; each harness maps them to its own event API.
505
505
 
506
+ ### Relationship to harness-native knowledge surfaces
507
+
508
+ Consumer harnesses typically maintain a harness-native primary knowledge surface — for example, a system-prompt layer, a persistent session notice, or a harness banner injected before every LLM call. The hook events and their guidance in §9 are **complementary to** this harness-native surface, not a replacement for it. When §9 says "inject X at hook Y", the concrete injection path may live in the harness-native surface if that produces an equivalent effect. What matters is that the described information reaches the agent at the described moment — the delivery mechanism is consumer-local.
509
+
506
510
  ### Event mapping examples
507
511
 
508
512
  Different harnesses expose these events under different names:
509
513
 
510
- - **Claude Code**: `SessionStart`, `UserPromptSubmit`, `SubagentStart`, `SubagentStop`, `PreToolUse`, `PostToolUse`, `Stop`, `PostCompact`
514
+ - **(illustrative, non-normative)** **Claude Code**: `SessionStart`, `UserPromptSubmit`, `SubagentStart`, `SubagentStop`, `PreToolUse`, `PostToolUse`, `Stop`, `PostCompact`
511
515
  - **OpenCode**: its own hook API names — map accordingly when building an OpenCode consumer
512
516
 
513
517
  Identify the equivalent events in your harness's plugin system and implement the expected behaviors below.
@@ -516,69 +520,66 @@ Identify the equivalent events in your harness's plugin system and implement the
516
520
 
517
521
  #### `session_start`
518
522
 
519
- **When it fires:** The harness launches or the user begins a new session.
523
+ **When it fires:** A new agent session begins (harness runtime may expose this as `SessionStart`, `session.created`, an init hook, or equivalent).
520
524
 
521
525
  **Expected consumer behavior:**
522
- - Create `.nexus/` and `.nexus/state/` directories if they do not exist.
523
- - Write `.nexus/.gitignore` with `state/` if it does not exist.
524
- - Create `.nexus/state/{harness-id}/` if it does not exist, then initialize `.nexus/state/{harness-id}/agent-tracker.json` as `[]`. `agent-tracker.json` is a shared-purpose session file whose path is namespaced per harness to prevent cross-harness collisions (see [nexus-outputs-contract.md §Shared filename convention](./nexus-outputs-contract.md)).
525
- - On v0.7.0+, harnesses SHOULD silently remove any legacy `.nexus/state/agent-tracker.json` (root) at session start. The file is session-scoped and legacy records are safely discarded.
526
- - Check for stale state from a prior crashed session: if `plan.json` or `tasks.json` exist, warn the user that these may be leftover from an unclean shutdown.
527
- - Load the knowledge index: list files in `.nexus/memory/`, `.nexus/context/`, and `.nexus/rules/` to build the reference index that will be injected into subagent spawns.
526
+ - **SHOULD** create `.nexus/` and `.nexus/state/` directories if they do not exist.
527
+ - **SHOULD** write `.nexus/.gitignore` with `state/` if it does not exist.
528
+ - **MUST** create `.nexus/state/{harness-id}/` if it does not exist, then initialize `.nexus/state/{harness-id}/agent-tracker.json` as `[]`. `agent-tracker.json` is a shared-purpose session file whose path is namespaced per harness to prevent cross-harness collisions (see [nexus-outputs-contract.md §Shared filename convention](./nexus-outputs-contract.md)).
529
+ - On v0.7.0+, harnesses **SHOULD** silently remove any legacy `.nexus/state/agent-tracker.json` (root) at session start. The file is session-scoped and legacy records are safely discarded.
528
530
 
529
531
  ---
530
532
 
531
533
  #### `user_message`
532
534
 
533
- **When it fires:** The user submits a message to Lead.
535
+ **When it fires:** The user submits a message and it is about to be processed (harness runtime may expose this as `UserPromptSubmit`, `message.received`, an on-input hook, or equivalent).
534
536
 
535
537
  **Expected consumer behavior:**
536
- - Scan the message text for bracket tags. Match against all triggers defined in `vocabulary/tags.yml`.
538
+ - **SHOULD** scan the message text for bracket tags. Match against all triggers defined in `vocabulary/tags.yml`.
537
539
  - For each matched tag:
538
- - If `type=skill`: activate the skill (see §8, steps 3–5). Do not proceed with normal message handling for that tag.
539
- - If `type=inline_action`: call the handler tool immediately. The user's message following the tag is the input.
540
- - After tag routing, inject contextual guidance into Lead's available context before LLM inference begins:
540
+ - **SHOULD** activate the skill if `type=skill` (see §8, steps 3–5). Do not proceed with normal message handling for that tag.
541
+ - **SHOULD** inject contextual guidance into Lead's available context before LLM inference begins:
541
542
  - Current plan status: if `plan.json` exists, summarize pending vs. decided issues.
542
543
  - Task progress: if `tasks.json` exists, summarize total/completed/pending counts and the ready-task set.
543
- - Knowledge file counts: number of files in `.nexus/memory/`, `.nexus/context/`, `.nexus/rules/`.
544
- - If no tags are matched, pass the message to Lead without modification.
544
+ - **SHOULD** pass the message to Lead without modification if no tags are matched.
545
545
 
546
546
  ---
547
547
 
548
548
  #### `subagent_spawn`
549
549
 
550
- **When it fires:** Lead spawns a subagent to execute a task.
550
+ **When it fires:** A subagent is created and about to begin execution (harness runtime may expose this as `SubagentStart`, `agent.spawned`, a subagent-init hook, or equivalent).
551
551
 
552
552
  **Expected consumer behavior:**
553
- - Record the new agent entry in `.nexus/state/{harness-id}/agent-tracker.json`: `{ harness_id, agent_name, agent_id, task_id, started_at }`.
554
- - Inject the knowledge index into the subagent's initial context: the list of files in `.nexus/memory/`, `.nexus/context/`, and `.nexus/rules/` so the agent knows what project knowledge is available.
555
- - Apply capability restrictions: resolve `effective_capabilities` for this agent type and configure the subagent's tool access accordingly (see §6).
556
- - Apply the resume evaluation: check `owner_reuse_policy` on the task and the agent's `resume_tier` to determine whether to spawn fresh or resume a prior session (see §10).
557
- - Pass the structured task context to the agent: title, context, approach, and acceptance criteria (see §10, Context Passing).
553
+ - **MUST** record the new agent entry in `.nexus/state/{harness-id}/agent-tracker.json`: `{ harness_id, agent_name, agent_id, started_at }`.
554
+ - **SHOULD** inject the knowledge index into the subagent's initial context: the list of files in `.nexus/memory/`, `.nexus/context/`, and `.nexus/rules/` so the agent knows what project knowledge is available.
555
+ - **SHOULD** apply capability restrictions: resolve `effective_capabilities` for this agent type and configure the subagent's tool access accordingly (see §6).
556
+ - **SHOULD** apply the resume evaluation: check `owner_reuse_policy` on the task and the agent's `resume_tier` to determine whether to spawn fresh or resume a prior session (see §10).
558
557
 
559
558
  ---
560
559
 
561
560
  #### `subagent_complete`
562
561
 
563
- **When it fires:** A subagent finishes its assigned work and returns control to Lead.
562
+ **When it fires:** A subagent finishes its assigned work and returns control (harness runtime may expose this as `SubagentStop`, `agent.completed`, a subagent-exit hook, or equivalent).
564
563
 
565
564
  **Expected consumer behavior:**
566
- - Update `.nexus/state/{harness-id}/agent-tracker.json`: set `status=completed`, record `stopped_at` timestamp.
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.
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.
570
- - Update the task status in `tasks.json` via the `task_update` tool: set to `completed`.
565
+ - **MUST** update `.nexus/state/{harness-id}/agent-tracker.json`: set `status=completed`, record `stopped_at` timestamp.
566
+ - **MUST** 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.
567
+ - **MAY** maintain a separate `edit-tracker.json` for cross-session file-touch history, updating it here. This is not a nexus-core requirement; it is a harness-local optimization.
568
+ - **SHOULD** surface an unfinished-task warning when the stopping agent has tasks left in `pending` or `in_progress` state. This pattern is observed across consumers and aids recovery; the warning may be injected into Lead's context or surfaced as a reminder.
569
+ - **MAY** update the task status in `tasks.json` when the agent was assigned a specific task. Note: this responsibility may alternatively be fulfilled at the Lead or tool-contract layer rather than the hook surface.
571
570
 
572
571
  ---
573
572
 
574
573
  #### `pre_tool_use`
575
574
 
576
- **When it fires:** A tool is about to execute.
575
+ **When it fires:** A tool call has been issued and is about to execute (harness runtime may expose this as `PreToolUse`, `tool.before`, a pre-call interceptor, or equivalent).
577
576
 
578
577
  **Expected consumer behavior:**
579
- - Gate enforcement for unplanned file edits: if `tasks.json` does not exist and the tool is a file-editing tool, block the call and return an error explaining that edits outside of a planned task cycle are disallowed. This prevents unplanned workspace changes.
580
- - Capability gate: check whether the current agent (Lead or a subagent) has the requested tool in its disallowed set. If so, block the call and return an appropriate error.
581
- - Any other pre-condition checks your harness requires (rate limits, sandbox policies, etc.).
578
+ - **SHOULD** enforce a gate for unplanned file edits: if `tasks.json` does not exist and the tool is a file-editing tool, block the call and return an error explaining that edits outside of a planned task cycle are disallowed. This prevents unplanned workspace changes.
579
+ - **Capability invariant (MUST)**: disallowed tools MUST NOT execute. **Enforcement layer (consumer choice)**: The abstract invariant holds regardless of enforcement layer. Consumers may enforce at the hook, in static agent config (frontmatter/TOML/disallowedTools), at a framework-level gate, or any combination the choice is consumer-local. What nexus-core requires is the invariant, not the enforcement mechanism.
580
+ - **MAY** apply any other pre-condition checks your harness requires (rate limits, sandbox policies, etc.).
581
+
582
+ > **SHOULD note on block reason**: When a tool call is blocked, the `reason` field returned to the LLM is consumed as operational guidance for the next action. Consumers SHOULD compose the `reason` as actionable, directive text rather than a generic error message.
582
583
 
583
584
  Read-only tools (query tools, status reads) are never blocked by capability gates. Only tools with primary write effects are subject to capability restrictions.
584
585
 
@@ -586,41 +587,53 @@ Read-only tools (query tools, status reads) are never blocked by capability gate
586
587
 
587
588
  #### `post_tool_use`
588
589
 
589
- **When it fires:** A tool has executed and returned a result.
590
+ **When it fires:** A tool call has completed and a result is available (harness runtime may expose this as `PostToolUse`, `tool.after`, a post-call interceptor, or equivalent).
590
591
 
591
592
  **Expected consumer behavior:**
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.
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.
594
- - If the tool result indicates an error, record the error in the log for diagnostic purposes. Do not suppress error results.
593
+ - **SHOULD** append a log entry to `.nexus/state/tool-log.jsonl`: timestamp, agent_id, tool name, file path (if a file was touched), result status. (This practice is observed across all consumers; nexus-core does not yet define a schema for this file — tracked for v0.11.0.)
594
+ - **MAY** record the file path and agent_id in `edit-tracker.json` if your harness maintains it for cross-session file-touch history. 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.
595
+ - **MAY** record the error in the log for diagnostic purposes if the tool result indicates an error. Do not suppress error results.
595
596
 
596
597
  ---
597
598
 
598
599
  #### `session_end`
599
600
 
600
- **When it fires:** The user closes the harness or the session terminates.
601
+ **When it fires:** The session is terminating (harness runtime may expose this as `Stop`, `session.end`, a shutdown hook, or equivalent).
601
602
 
602
603
  **Expected consumer behavior:**
603
- - Check for pending tasks: if `tasks.json` exists and contains incomplete tasks (status `pending` or `in_progress`), warn the user that the session is ending with unfinished work and suggest calling `task_close` to archive before exiting.
604
- - Check for an active plan: if `plan.json` exists, warn that the plan session will be lost if not archived.
605
- - Delete `.nexus/state/{harness-id}/agent-tracker.json` (a session-scoped file that has no value beyond the session).
606
- - Optionally rotate or archive `tool-log.jsonl` if your harness supports log retention.
607
- - Do not delete `history.json`, `memory/`, `context/`, or `rules/` these are project-scoped and must persist.
604
+ - **SHOULD** warn the user if `tasks.json` exists and contains incomplete tasks (status `pending` or `in_progress`), that the session is ending with unfinished work and suggest calling `task_close` to archive before exiting.
605
+ - **SHOULD** delete `.nexus/state/{harness-id}/agent-tracker.json` (a session-scoped file that has no value beyond the session).
606
+ - **MAY** rotate or archive `tool-log.jsonl` if your harness supports log retention.
607
+ - **MUST NOT** delete `history.json`, `memory/`, `context/`, or `rules/` these are project-scoped and must persist across sessions.
608
+ - **SHOULD** prompt or block with an "all tasks completed — call `task_close` to archive" reminder when `tasks.json` shows all tasks completed but the cycle has not been archived via `task_close`. Run-cycle closure is a recoverability boundary.
608
609
 
609
610
  ---
610
611
 
611
612
  #### `context_compact`
612
613
 
613
- **When it fires:** The LLM's context window is compressed (older messages are truncated to make room for new content).
614
+ **When it fires:** The runtime compresses the context window (harness runtime may expose this as `PostCompact`, `context.compacted`, a post-compact hook, or equivalent).
614
615
 
615
616
  **Expected consumer behavior:**
616
- - Re-inject the critical session snapshot that was lost in compression:
617
+ - **SHOULD** re-inject the critical session snapshot that was lost in compression:
617
618
  - Active skill/mode: which skill is currently active (plan, run, sync, or none).
618
619
  - Plan status: if `plan.json` exists, re-inject the issue list with pending/decided status.
619
620
  - Task progress: if `tasks.json` exists, re-inject the task list with status and ready-task set.
620
621
  - Knowledge file index: re-inject the list of files in `.nexus/memory/`, `.nexus/context/`, `.nexus/rules/`.
621
622
  - Active agent list: re-inject which subagents are currently tracked in `.nexus/state/{harness-id}/agent-tracker.json`.
622
- - Context compaction is a context loss event. The LLM cannot reconstruct session state from its compressed context alone. Your consumer must restore state from the state files on disk.
623
- - Read state files fresh from disk — do not rely on in-memory caches that may also have been cleared.
623
+ - **SHOULD** restore state from the state files on disk. Context compaction is a context loss event; the LLM cannot reconstruct session state from its compressed context alone.
624
+ - **SHOULD** read state files fresh from disk — do not rely on in-memory caches that may also have been cleared.
625
+
626
+ ---
627
+
628
+ ### 9.X Appendix: Hook Event Runtime Mapping (consumer-owned)
629
+
630
+ Each consumer harness MUST publish a mapping document at `harness-content/nexus-hook-mapping.md` in its own repository, listing how its runtime hook surface maps to the 8 conceptual events in §9.
631
+
632
+ nexus-core does not mirror these mappings — they are owned and maintained per consumer. This appendix is a registry pointer, not a copy.
633
+
634
+ - **harness_docs_refs token**: `nexus_hook_mapping`
635
+ - **Expected location in consumer repo**: `harness-content/nexus-hook-mapping.md`
636
+ - **Descriptive, not normative**: the consumer mapping document reflects current harness implementation. Verify against the consumer repo for the current mapping — this appendix does not guarantee specific contents.
624
637
 
625
638
  ---
626
639
 
@@ -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 정체성)
@@ -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/` 디렉토리 — 에이전트 생성 파일
@@ -216,6 +243,7 @@ Nexus 산출물은 생성 책임 주체에 따라 세 카테고리로 분류된
216
243
  ├── artifacts/
217
244
  ├── claude-nexus/ ← namespace 디렉토리
218
245
  │ ├── agent-tracker.json ← shared-purpose file (§Shared filename convention)
246
+ │ ├── memory-access.jsonl ← shared-purpose file (§Shared filename convention)
219
247
  │ ├── plan.extension.json ← plan.json의 확장 (priority, estimated_effort 등)
220
248
  │ ├── tasks.extension.json
221
249
  │ ├── history.extension.json ← 하네스 자체 archive
@@ -224,6 +252,7 @@ Nexus 산출물은 생성 책임 주체에 따라 세 카테고리로 분류된
224
252
  │ └── tool-log.jsonl
225
253
  └── opencode-nexus/ ← sibling 하네스 namespace 디렉토리
226
254
  ├── agent-tracker.json ← shared-purpose file (동일 파일명, harness-local)
255
+ ├── memory-access.jsonl ← shared-purpose file (동일 파일명, harness-local)
227
256
  └── ...
228
257
  ```
229
258
 
@@ -268,6 +297,7 @@ nexus-core는 shared-purpose file에 대해 **최소 공통 schema contract**를
268
297
  | 파일명 | schema | 목적 | 최초 적용 |
269
298
  |---|---|---|---|
270
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 |
271
301
 
272
302
  향후 신규 shared-purpose file을 추가할 때는 반드시 이 섹션에 등록해야 한다. 등록 없이 여러 하네스가 동일 파일명을 독립적으로 사용하는 것은 MUST NOT 허용되어서는 안 된다.
273
303