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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- commit, PR, review 기준: `docs/contribution-guide.md`
- 작업 회고, memory 후보, 승인 기반 자가진화 정책: `docs/self-evolution-guide.md`
- 개인 맥락 기억 prototype의 목표, 비목표, 저장/검색/평가 기준: `docs/subbrain-design.md`
- LLM Wiki 지식 검색, 인용 근거, answer task 결합: `docs/wiki-knowledge.md`
- LLM Wiki 저장 구조, page schema, lint 규칙: `packages/wiki/src/index.ts`

## Wiki 기억 사용
Expand Down
15 changes: 9 additions & 6 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ LLM Wiki는 관리 영역에 복사한 source와 사람이 읽을 수 있는 재
```bash
pnpm cli wiki init
pnpm cli wiki source add notes.md --title "조사 노트"
pnpm cli wiki answer task "무엇을 재사용 지식으로 남길까?" \
--sources <source-id> --out task.json
pnpm cli wiki knowledge retrieve "무엇이 지속 가능한 경쟁 우위를 만드는가?"
pnpm cli wiki answer task "무엇이 지속 가능한 경쟁 우위를 만드는가?" --out task.json

# .ai-lab/wiki-exchange/task.json의 prompt를 원하는 AI에 전달한다.
# AI가 반환한 JSON을 .ai-lab/wiki-exchange/result.json으로 저장한다.
Expand All @@ -80,7 +80,9 @@ pnpm cli wiki answer apply proposal.json \
--reviewer "<이름>" --accept-digest "<검토한-전체-digest>"
```

task에는 선택한 source 원문, Wiki schema와 index, 최대 5개의 관련 page가 들어간다.
answer task는 최대 5개의 active 지식 page를 검색하고 그 page가 가리키는 raw source를
인용 가능한 근거로 묶는다. `--sources <source-id>`는 선택 사항이며 근거를 추가한다.
task에는 Wiki schema와 index도 들어간다.
구독형 서비스나 다른 모델에 전달하기 전에 공개 내용을 확인해야 한다. 같은 엄격한 result
규약을 웹 구독, 로컬 모델, 신뢰된 runner wrapper가 공유한다. task와 proposal 생성은 실제
Wiki page를 바꾸지 않는다. host의 runner 흐름은 result artifact만 만들며 proposal과
Expand All @@ -107,9 +109,9 @@ lint, 검토한 byte를 다시 확인하고 승격과 audit 기록을 수행한
기존 source·concept page의 비파괴 재생성, 비교, digest 승인 기반 승격 절차는
`docs/wiki-rebuild.md`에 있다.

source 선택은 신뢰된 integration이 소유한다. agent-safe tool은 source를 가져오거나 외부
전달용 task를 만들거나 proposal을 apply할 수 없다. 경로 이탈, symbolic link, 오래된 task,
알 수 없는 evidence ID, 과도하게 큰 artifact, 잘못된 교환 데이터는 거부한다.
명시적 source 추가는 신뢰된 integration이 소유한다. agent-safe tool은 source를 가져오거나
외부 전달용 task를 만들거나 proposal을 apply할 수 없다. 경로 이탈, symbolic link, 오래된
task, 알 수 없는 evidence ID, 과도하게 큰 artifact, 잘못된 교환 데이터는 거부한다.

반복 사용할 코드는 `packages/*`에 둔다. 사람이 직접 실행해야 하는 흐름만 `apps/cli` 또는 `apps/service`에서 노출한다. provider SDK나 외부 runner 세부사항은 `packages/model-providers` 안에 격리한다.

Expand All @@ -130,6 +132,7 @@ pnpm check
- `docs/testing-guide.md`
- `docs/external-runner.md`
- `docs/subscription-runner.md`
- `docs/wiki-knowledge.md`
- `docs/contribution-guide.md`
- `docs/self-evolution-guide.md`
- `docs/subbrain-design.md`
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ portable answer workflow does not call a model API or depend on one AI vendor:
```bash
pnpm cli wiki init
pnpm cli wiki source add notes.md --title "Research notes"
pnpm cli wiki answer task "What should remain reusable?" \
--sources <source-id> --out task.json
pnpm cli wiki knowledge retrieve "What creates durable advantage?"
pnpm cli wiki answer task "What creates durable advantage?" --out task.json

# Give the prompt in .ai-lab/wiki-exchange/task.json to any AI.
# Save its JSON response as .ai-lab/wiki-exchange/result.json.
Expand All @@ -80,8 +80,9 @@ pnpm cli wiki answer apply proposal.json \
--reviewer "<name>" --accept-digest "<full-reviewed-digest>"
```

The task artifact contains the selected sources, the Wiki schema and index, and up to five matched
pages. Inspect the disclosure before sharing them with a subscription service or another model.
The answer task retrieves up to five active knowledge pages and binds their raw sources as citable
evidence. `--sources <source-id>` remains an optional, additive override. The task also contains the
Wiki schema and index. Inspect the disclosure before sharing it with another service or model.
The same strict result schema works with web subscriptions, local models, and trusted runner
wrappers. Task and proposal creation do not change live Wiki pages. The host-side runner workflow
only creates a result artifact; proposal and apply remain separate commands.
Expand All @@ -108,9 +109,9 @@ Existing source and concept pages can also be regenerated as non-mutating shadow
compared with their baseline, and promoted only from an exact digest-approved task, result, and
report. See `docs/wiki-rebuild.md`.

Trusted integrations own source selection. Agent-safe tools cannot import sources, create outbound
tasks, or apply proposals. The package rejects traversal, symbolic links, stale tasks, unknown
evidence IDs, oversized artifacts, and malformed exchange data.
Trusted integrations own explicit source overrides. Agent-safe tools cannot import sources, create
outbound tasks, or apply proposals. The package rejects traversal, symbolic links, stale tasks,
unknown evidence IDs, oversized artifacts, and malformed exchange data.

Implement reusable code in `packages/*`, expose human-facing flows from `apps/cli` or `apps/service` only when they are meant for people, and keep provider-specific SDK details inside `packages/model-providers`.

Expand All @@ -123,6 +124,7 @@ Implement reusable code in `packages/*`, expose human-facing flows from `apps/cl
- `docs/testing-guide.md`
- `docs/external-runner.md`
- `docs/subscription-runner.md`
- `docs/wiki-knowledge.md`
- `docs/contribution-guide.md`
- `docs/self-evolution-guide.md`
- `docs/subbrain-design.md`
Expand Down
52 changes: 49 additions & 3 deletions apps/cli/src/wiki.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
type WikiAnswerRunnerResult,
type WikiAnswerTask,
WikiAnswerWorkflow,
WikiKnowledgeWorkflow,
type WikiMemoryComparisonRunInput,
WikiMemoryWorkflow,
type WikiProposal,
Expand Down Expand Up @@ -142,11 +143,16 @@ async function dispatchWikiCommand(
return answerReviewCommand(root, args[2] ?? "");
if (route === "answer apply" && args.length === 3)
return answerApplyCommand(root, args[2] ?? "", options);
if (route.startsWith("rebuild ") || route.startsWith("reflect ") || route.startsWith("memory "))
return dispatchWikiMaintenanceCommand(root, args, options);
if (maintenanceRoute(route)) return dispatchWikiMaintenanceCommand(root, args, options);
throw new Error(`Unknown wiki command: wiki ${args.join(" ")}`);
}

function maintenanceRoute(route: string): boolean {
return ["rebuild ", "reflect ", "knowledge ", "memory "].some((prefix) =>
route.startsWith(prefix),
);
}

async function dispatchWikiMaintenanceCommand(
root: string | undefined,
args: readonly string[],
Expand All @@ -167,6 +173,8 @@ async function dispatchWikiMaintenanceCommand(
return reflectionReviewCommand(root, args[2] ?? "");
if (route === "reflect apply" && args.length === 3)
return reflectionApplyCommand(root, args[2] ?? "", options);
if (route === "knowledge retrieve" && args.length === 3)
return knowledgeRetrieveCommand(root, args[2] ?? "", options);
return dispatchWikiMemoryCommand(root, args, options);
}

Expand Down Expand Up @@ -217,7 +225,7 @@ async function answerTaskCommand(
function taskInput(question: string, options: TaskOptions) {
const input = {
question,
sourceIds: sourceIds(requiredText(options.sources, "--sources")),
sourceIds: options.sources === undefined ? [] : sourceIds(options.sources),
};
return options.title === undefined ? input : { ...input, title: options.title };
}
Expand Down Expand Up @@ -550,6 +558,38 @@ async function memoryRetrieveCommand(
console.log(JSON.stringify(memoryContextSummary(context, artifact), null, 2));
}

async function knowledgeRetrieveCommand(
root: string | undefined,
query: string,
options: TaskOptions,
): Promise<void> {
const context = await knowledgeWorkflow(root).prepareContext(query);
if (options.out === undefined) {
console.log(JSON.stringify(context, null, 2));
return;
}
const artifact = await writeArtifact(workspaceRoot(root), options.out, context);
console.log(JSON.stringify(knowledgeContextSummary(context, artifact), null, 2));
}

function knowledgeContextSummary(
context: Awaited<ReturnType<WikiKnowledgeWorkflow["prepareContext"]>>,
artifact: string,
) {
return {
artifact,
id: context.id,
digest: context.digest,
knowledge: context.knowledge.map(({ path, kind, score, matchedTerms, sources }) => ({
path,
kind,
score,
matchedTerms,
sources,
})),
};
}

function memoryContextSummary(
context: Awaited<ReturnType<WikiMemoryWorkflow["prepareContext"]>>,
artifact: string,
Expand Down Expand Up @@ -654,6 +694,12 @@ function memoryWorkflow(root?: string): WikiMemoryWorkflow {
);
}

function knowledgeWorkflow(root?: string): WikiKnowledgeWorkflow {
return new WikiKnowledgeWorkflow(
root === undefined ? createDefaultWorkspace() : createWorkspace(root),
);
}

function workspaceRoot(root?: string): string {
return root ?? createDefaultWorkspace().root;
}
Expand Down
80 changes: 80 additions & 0 deletions apps/cli/test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,62 @@ describe("cli", () => {
);
});

it("retrieves Wiki knowledge and prepares an answer without explicit source ids", async () => {
const root = await tempRoot();
const log = vi.spyOn(console, "log").mockImplementation(() => undefined);
await writeFile(join(root, "source.md"), "# Research\nLearning loops create durable value.\n");
await runCli(["node", "ai-lab", "wiki", "init"], root);
await runCli(
["node", "ai-lab", "wiki", "source", "add", "source.md", "--title", "Research"],
root,
);
const source = loggedJson<{ id: string; path: string }>(log);
await writeFile(
join(root, "wiki", "pages", "concepts", "learning-loop.md"),
knowledgePage(relative(join(root, "wiki"), source.path)),
"utf8",
);

await runCli(
[
"node",
"ai-lab",
"wiki",
"knowledge",
"retrieve",
"학습 루프가 만드는 가치",
"--out",
"knowledge-context.json",
],
root,
);
const context = await artifact<{ knowledge: { path: string }[] }>(
root,
"knowledge-context.json",
);
expect(context.knowledge.map(({ path }) => path)).toEqual(["pages/concepts/learning-loop.md"]);

await runCli(
[
"node",
"ai-lab",
"wiki",
"answer",
"task",
"학습 루프가 만드는 가치는 무엇인가?",
"--out",
"knowledge-task.json",
],
root,
);
const task = await artifact<WikiAnswerTask>(root, "knowledge-task.json");
expect(task.requestedSourceIds).toEqual([]);
expect(task.knowledge.map(({ path }) => path)).toEqual(["pages/concepts/learning-loop.md"]);
expect(task.evidence).toEqual([
{ id: source.id, path: relative(join(root, "wiki"), source.path) },
]);
});

it("retrieves, injects, and evaluates reviewed Wiki memory without a model API", async () => {
const root = await tempRoot();
const log = vi.spyOn(console, "log").mockImplementation(() => undefined);
Expand Down Expand Up @@ -1187,6 +1243,30 @@ Review durable knowledge before reuse.
`;
}

function knowledgePage(source: string): string {
return `---
title: Learning Loop
slug: learning-loop
kind: concept
status: active
createdAt: 2026-06-17T12:00:00.000Z
updatedAt: 2026-06-17T12:00:00.000Z
reviewAfter: 2027-06-17T12:00:00.000Z
sources:
- ${source}
---

## Summary

학습 루프는 실행과 결과를 연결해 가치를 개선한다.

## Key Claims

- accepted: Learning loops create durable value.
source: ${source}
`;
}

function questionPath(root: string): string {
return join(root, "wiki", "pages", "questions", "what-is-durable-knowledge.md");
}
9 changes: 4 additions & 5 deletions docs/system-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ docs/
- `packages/model-providers`: provider adapters and routing. It supports API, external runner, manual, and fake provider kinds. It implements deterministic fake providers, the strict process boundary for trusted external-runner wrappers, and exact-version Codex and Claude subscription CLI profiles.
- `packages/agent-runtime`: agent execution flow and trusted application workflows. It calls model providers and local tools, returns normalized run results, and composes the provider-neutral Wiki answer flow for human-facing adapters. It does not know CLI, HTTP, MCP, or provider transport details.
- `packages/workspace`: local workspace behavior such as root selection, slug creation, and path-oriented helpers.
- `packages/wiki`: local markdown LLM Wiki behavior such as wiki layout, source registration, portable task/result schemas, digest-bound answer and reflection proposals, approved-memory retrieval, no-memory control tasks and paired evaluation records, non-mutating shadow rebuild reports, approval and stale-hash gates, transactional promotion, audit logs, metadata, and deterministic linting. Trusted integrations own source selection and reviewer authentication. The package has no provider, process, network, agent-loop, or CLI knowledge.
- `packages/wiki`: local markdown LLM Wiki behavior such as wiki layout, source registration, deterministic knowledge and approved-memory retrieval, portable task/result schemas, digest-bound answer and reflection proposals, no-memory control tasks and paired evaluation records, non-mutating shadow rebuild reports, approval and stale-hash gates, transactional promotion, audit logs, metadata, and deterministic linting. Trusted integrations own explicit source overrides and reviewer authentication. The package has no provider, process, network, agent-loop, or CLI knowledge.
- `packages/subbrain`: portable personal context memory prototype. It owns raw manual entries, event-level memories, the store interface, deterministic retrieval scoring, context packets, replaceable extraction/linking/query/answer ports, fixtures, and evaluation helpers. Its SQLite implementation is exposed from a separate subpath. It must not depend on apps, wiki, model providers, or agent runtime.
- `packages/local-tools`: tools callable by the agent runtime, such as echo and Wiki packet/proposal tools. Its default agent-safe Wiki set cannot import sources, export source-bearing tasks, or apply proposals.
- `apps/cli`: human terminal entrypoint. It owns private exchange artifacts, outbound task and runner disclosure, exact runner consent, exact proposal review rendering, and explicit digest acceptance.
Expand Down Expand Up @@ -99,10 +99,9 @@ still cannot prove per-request quota or billing. The contracts are in `docs/exte

- Re-audit built-in subscription profiles for new exact CLI versions, and add another profile only
when its prompt transport, authentication route, and tool controls preserve the shared contracts.
- Extend language normalization beyond the current narrow Korean-particle rule, or add embeddings
or graph retrieval, only after reviewed multilingual retrieval terms and deterministic memory
evaluation records show that lexical retrieval still misses useful pages often enough to justify
the added complexity.
- Extend language normalization, embeddings, or graph retrieval only after deterministic knowledge
retrieval tests and reviewed memory evaluation records show enough lexical misses to justify the
added complexity.
- Extend `packages/subbrain` with embedding search, graph traversal, and relationship context after the deterministic baseline passes.
- Add `packages/mcp` when agent runtime, local tools, or workspace capabilities need to be exposed to external agents.
- Add `packages/evals` or `evals/` when the same validation logic repeats across multiple wiki or agent runs.
Expand Down
4 changes: 4 additions & 0 deletions docs/testing-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ creation and proposal preparation leave the live Wiki unchanged, stale or forged
source IDs remain bound to selected evidence, and only an exact human-approved digest is promoted.
CLI tests must not open a browser, call an API, or invoke a subscription tool.

Knowledge tests verify active and review-date eligibility, deterministic field-weighted ranking,
Korean suffix normalization, the five-page limit, raw-source expansion, answer-task binding, and
stale page rejection. Compiled pages guide synthesis but cannot replace bound raw evidence.

Memory tests verify active and review-date eligibility, reviewed multilingual retrieval terms,
deterministic relevance ranking, the three-page limit, answer-task injection, stale hashes, exact
per-page assessments, digest-bound no-memory controls, paired result hashes and preferences, and
Expand Down
Loading
Loading