Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
### CI 完成确认

<!-- 用 RCP/forge 观察确认,不逐个 job 轮询。RCP 节的
`nmg-rcp forge-status --pr <PR号>` 是权威读法;下面两项是其补充。 -->
`nmg-rcp forge-status --pr <PR号>` 是权威读法;下面两项是其补充。
CI 从 push 到全部检查完成通常约一分半,可稍等后 `nmg-rcp forge-status --pr <PR号>` 复查,
不要按固定时长人工反复轮询或把“看起来在跑”当作“已通过”。 -->

- [ ] `nmg-rcp forge-status --pr <PR号>` 的 `All checks passed` 为 SUCCESS
(或 CI Status Snapshot `.nmg-ci/status.json`:`conclusion: "success"` 且 `failures: []`)
Expand Down
49 changes: 42 additions & 7 deletions docs/design/agent-convergence-feedback-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,40 @@ model, and the candidate design that would close the remaining loop. It is
written so a future Agent can pick the thread up without re-deriving the
conversation.

## Implementation boundary (partial, not design completion)

The first code slice implements only:

- `src/rcp/session-feedback.ts`: a validated receipt becomes a bounded temporary
tool observation in an explicitly named session/task frame. CLI opt-in uses
`reconcile ... --nmg optional --session-id <id> --task-frame-id <id>` and an
already-running compatible daemon. It does not start/stop a daemon, write
durable memory, mark a quest complete, or train a router. Receipt delivery
failures are diagnostics, not changes to the verifier's decision. Plan mode
and idempotent receipt reuse do not emit a new notification. AG visibility
still follows the existing activation/disclosure lifecycle.
- `src/lab/context-router.ts`: an isolated 132-parameter action-value primitive
using the existing autodiff substrate, allowed-action selection, explicit
epsilon sampling with propensity, detached weight export, and observed-action
regression. It is not connected to automatic recall, not exposed as a new Lab
lease, and not a trained or enabled policy. Outcome admission remains the
caller's responsibility; arbitrary numeric rewards are not verified evidence.

`tests/rcp/session-feedback.test.ts` exercises actual failed npm verification,
receipt/contract binding, duplicate projection, session isolation, outage
handling, plan behavior, and CLI-to-daemon HTTP delivery in a controlled fixture.
`tests/core/context-router.test.ts` covers the small head in interpreted and
compiled execution. These tests establish their exercised mechanisms, not
natural task success, convergence, or causal intervention benefit.

Still open: the unified lifecycle/event and intervention sample pipeline,
versioned feature schema, four executor integration, validated outcome admission,
bookmark content ingestion, long-lived quests, held-out learning experiments,
and all default-activation gates. The original theoretical discussion below
remains a draft; in particular, lifecycle facts and SimHash are **not** admitted
as universally unbiased task rewards. Do not close the overall design task on
this slice or its PR.

## 1. The replaced model

Popular writing frames long agent tasks as a reliability product: if each step
Expand Down Expand Up @@ -467,10 +501,10 @@ immutable checkpoints; authority (plan/apply) sizes the step.
Existing: contract/observe/reconcile, idempotent re-observation, changedPaths,
fail-closed termination, receipts.

Gaps: reconcile drift events do **not flow back** — neither into the session
AG as observations nor into searchable memory for a later session. Continuous
observation is deferred (correctly, until run-to-completion feedback latency is
demonstrated fatal).
Partial wiring: explicit CLI opt-in sends a newly recorded receipt into a named
session AG as bounded feedback (see the implementation boundary above). General
drift/lifecycle streaming, retry replay and cross-session memory admission remain
open. Continuous observation is deferred until its need is measured.

### 5.3 AG (session Active Graph runtime)

Expand All @@ -485,8 +519,8 @@ projections (temporary, deduped, budgeted), typed edge layers that stop
activation/reasoning from silently reinforcing semantic truth. Item kinds:
`semantic_memory | tool_observation | board_projection | reasoning_artifact`.

Gaps: reconcile events are not wired into `observe()`; external content
fragments are not a first-class ingestible kind — AG ingests tool observations
Partial wiring: RCP receipts can reach `observe()` through the explicit optional
client bridge. External content fragments are not a first-class ingestible kind — AG ingests tool observations
and board projections, but a bookmark (§5.4), an external snippet + path,
cannot be added as content, and when a memory enters the AG as a
`semantic_memory` item only its `statement` travels (its bookmarks do not ride
Expand Down Expand Up @@ -580,7 +614,8 @@ elaborates and decomposes, it does not self-initiate). Consequences:
half of this.
2. AG cannot ingest external content fragments; bookmark ingestion is the
concrete first case (memory items enter the AG without their bookmarks).
3. Reconcile drift events are not wired into AG `observe()`.
3. Explicit newly recorded RCP receipt → AG `observe()` is implemented; general
drift streaming, notification replay and automatic harness feedback remain open.
4. No intervention logging or ε-greedy exploration exists to make `u(d,p)`
estimable (counterfactual data). Reuse pattern: learned-router gates
(`examples ≥ N`, lexical guard, shadow-first) plus an always-random probe
Expand Down
15 changes: 15 additions & 0 deletions docs/design/ci-cd-and-quality.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,21 @@ revision,避免控制面输出改变自身输入。
当前只实现 run-to-completion 路径。`continuous` 是 Contract 可声明的权限上限,不表示
已经存在 watcher;在出现真实持续 contract 前,常驻 queue/catalog/watcher 仍明确延后。

#### 可选的会话反馈

`reconcile` 可显式附加 `--nmg optional --session-id <id> --task-frame-id <id>`,
把本次已落盘的 receipt 投影到调用方指定的会话 AG。两项身份必须同时给出,不能以
Contract ID 猜测会话。该路径只连接 `NMG_DATA_DIR` 下已经运行且协议/能力兼容的
`nmg.sqlite` daemon,不启动、替换或停止它;也不执行普通 NMG recall 或发布黑板结果。

反馈保留 receipt ID、Contract digest、observed revision、检查结果与有界诊断,作为
`tool_observation` 遵守既有 AG 激活、去重和会话清理规则。receipt 仍是完整证据源,
AG 摘要不是任务完成标签,也不会自动进入 LTG 或控制器训练。收据完整性与成功条件分开:
scope 越界的 `failed` 收据可以保存并反馈,但不能作为成功复用;`verified` 收据仍须 scope
匹配。RCP 通过随包 CLI 的 `nmg session observe` 通讯,不导入 NMG 内部模块。plan 和 receipt reuse
不产生新通知;反馈失败只记入 `memoryDiagnostics`,不会改写验证结论。当前边界不含
通用事件总线、自动重试投递、跨会话任务续接或学习策略激活。

### 7.12 CI 状态观察

`.github/workflows/ci-status.yml` 在规范 `CI` workflow 完成后运行一个只读 GitHub-hosted
Expand Down
16 changes: 16 additions & 0 deletions docs/design/completion-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ to use it explicitly for one suitable task.
| Repository Control Plane | External Contract-to-Agent-to-Code-to-Verifier run-to-completion reconciliation with separate desired, observed, receipt and forge truth domains; optional one-way NMG integration | `src/rcp/`, `nmg-rcp` CLI, standard `agent:verify` auto-delegation for uniquely covered scopes, shared context/verification primitives, deterministic RCP tests, validated receipt reuse, verifier/route binding, fail-closed Git/forge provenance, local receipt scan, bounded process harness and explicit interrupted-attempt recovery | **Verified core** | The default receipt store and in-flight journal are local and Git-ignored: they support operator audit, idempotency and replay-safe recovery, not portable third-party proof. Unmatched scopes keep ordinary verification; overlapping Contracts fail closed. Continuous reconciliation and external attestation are explicitly deferred. |
| Cloud and sandbox | Pluggable future integration only; no default dependency | explicit product boundary | **Out of scope** | Pi sandbox plugins own execution isolation; cloud waits for multi-device scope. |

## Agent convergence feedback implementation slice

The [feedback design](agent-convergence-feedback-design.md) remains **partial**,
not completed by a documentation or implementation PR. `SessionFeedbackProvider`
and the explicit RCP CLI session/task target send validated, newly recorded
receipts into temporary AG observations. Controlled tests cover a real failing
check, HTTP delivery, identity binding, deduplication and failure isolation.
They do not prove overall task completion or natural Agent improvement.

The 132-parameter `ContextRouter` numerical primitive has action-mask, sampling,
observed-action update and weight round-trip tests, including compiled execution.
It is not automatically invoked, trained from natural outcomes, or exposed as a
new self-service Lab capability. Unified event/sample collection, fixed feature
semantics, executor integration, outcome admission and held-out efficacy remain
open. No completed status or default promotion is implied.

## Actual active blockers

Four active work families remain; the first is narrower AG completion work and
Expand Down
6 changes: 5 additions & 1 deletion docs/design/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ NMG through an optional client adapter, but the dependency direction is strictly
`Repository Control Plane -> NMG client -> NMG daemon`. Git contracts, repository
observations, verification receipts, pull-request state, and merge readiness
remain control-plane or forge concerns; they do not become STG/LTG truth and the
NMG daemon never schedules or reconciles repository work. The implemented core
NMG daemon never schedules or reconciles repository work. An explicit RCP client
may project a validated receipt into a named session/task frame as bounded
`tool_observation` feedback through the existing AG RPC. This does not infer
quest completion, admit a training reward, or transfer verification authority to
NMG; full evidence remains in the receipt. The implemented core
and deferred boundary are specified in
[ci-cd-and-quality.md](ci-cd-and-quality.md#7-repository-control-plane) and the
implemented [Repository Control Plane decision](../decisions/implemented/2026-08-29-repository-control-plane.md).
Expand Down
22 changes: 22 additions & 0 deletions src/cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type {
NmgRollbackNodeTransformParams,
NmgRetentionCandidatesParams,
NmgSearchParams,
NmgSessionActiveGraphParams,
NmgSetStorageStateParams,
NmgSplitNodeParams,
NmgSyncStgParams,
Expand All @@ -44,6 +45,8 @@ export interface CliCommandSpec {
method?: NmgMethod;
/** Local command dispatched by main.ts without RPC (e.g. the inspect TUI). */
local?: boolean;
/** Session-owned state is meaningful only in an existing resident daemon. */
requiresResident?: boolean;
/** Set false to reject COMMON_FLAGS (e.g. --json) for this command. */
includeCommonFlags?: boolean;
/** CLI words, e.g. ["search"] or ["retention", "candidates"]. */
Expand All @@ -64,6 +67,25 @@ export const COMMON_OPTIONS = ["data-dir", "db"] as const;
export const COMMON_FLAGS = ["json"] as const;

export const NMG_CLI_COMMANDS: readonly CliCommandSpec[] = [
{
method: "sessionActiveGraph",
words: ["session", "observe"],
requiresResident: true,
usageLine:
"nmg session observe TEXT --session-id ID --task-frame-id ID --source-id ID [--json]",
options: ["session-id", "task-frame-id", "source-id"],
flags: [],
usageDetail:
"Session observations require an already-running compatible daemon. They are temporary tool observations, not durable memories or completion labels.",
buildParams: (values): NmgSessionActiveGraphParams => ({
action: "observe",
statement: singlePositional(values, "session observe"),
sessionId: requiredOption(values, "session-id"),
taskFrameId: requiredOption(values, "task-frame-id"),
sourceId: requiredOption(values, "source-id"),
kind: "tool_observation",
}),
},
{
method: "lab",
words: ["lab", "list"],
Expand Down
42 changes: 27 additions & 15 deletions src/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import { NmgService } from "./service.ts";
import { histogramQuantile } from "../core/perf.ts";
import type { MemoryContext } from "../core/types.ts";
import { compactSearchContext } from "../integration/search-projection.ts";
import { assertDaemonProtocol } from "./daemon-client.ts";
import { assertDaemonCapability, assertDaemonProtocol, parseDaemonHello } from "./daemon-client.ts";

// The CLI surface (synopsis, option details, known options/flags) is
// assembled from the command registry in commands.ts; only the daemon
Expand Down Expand Up @@ -115,20 +115,7 @@ export async function runCli(
}

try {
const state = readServerState(serverStatePath(service.databasePath));
let result: unknown;
if (state?.transport === "http" && isProcessAlive(state.pid)) {
if (state.protocol !== NMG_PROTOCOL_VERSION) {
assertDaemonProtocol((await httpCall(state, "hello")) as NmgHelloResult);
}
result = await httpCall(
state,
parsed.command,
(parsed.params ?? {}) as Record<string, unknown>,
);
} else {
result = await service.invoke(parsed.command, parsed.params);
}
const result = await invokeCliMethod(service, parsed.command, parsed);
const output =
parsed.compactJson && parsed.command === "search"
? compactSearchContext(result as MemoryContext)
Expand All @@ -147,6 +134,29 @@ export async function runCli(
}
}

async function invokeCliMethod(
service: NmgService,
method: NmgMethod,
parsed: ParsedArguments,
): Promise<unknown> {
const state = readServerState(serverStatePath(service.databasePath));
if (state?.transport === "http" && isProcessAlive(state.pid)) {
if (parsed.requiresResident) {
const hello = parseDaemonHello(await httpCall(state, "hello"));
assertDaemonCapability(
new Set(hello.capabilities),
method,
hello.methods ? new Set(hello.methods) : undefined,
);
} else if (state.protocol !== NMG_PROTOCOL_VERSION) {
assertDaemonProtocol((await httpCall(state, "hello")) as NmgHelloResult);
}
return httpCall(state, method, parsed.params ?? {});
}
if (parsed.requiresResident) throw new Error("session observations require a running NMG daemon");
return service.invoke(method, parsed.params);
}

async function runDaemonCommand(
command: DaemonCommand,
service: NmgService,
Expand Down Expand Up @@ -306,6 +316,7 @@ interface ParsedArguments {
| NmgPerfParams;
json: boolean;
compactJson?: boolean;
requiresResident?: boolean;
dataDirectory?: string;
databasePath?: string;
/** Local `graph` command: output HTML path. */
Expand Down Expand Up @@ -365,6 +376,7 @@ function parseArguments(argv: readonly string[]): ParsedArguments {
}
return {
command: spec.method!,
requiresResident: spec.requiresResident,
params: spec.buildParams(values) as ParsedArguments["params"],
json: values.flags.has("json"),
compactJson: values.flags.has("compact-json"),
Expand Down
Loading