Skip to content
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,5 @@ docs/*/*
# Ignore all memories (keep folder marker only)
memories/*
!memories/.gitkeep
memories/vault/*
!memories/vault/.gitkeep
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ All skills are under `.claude/skills/`. Any agent can use any skill when relevan
## Consensus Memory

- `memories/consensus.md` - cross-cycle baton; must be updated before cycle end
- `memories/vault/` - persistent vector memory index (`index.json`), auto-built by
`scripts/core/memory_vault.py` from consensus snapshots + `docs/` outputs. Every
cycle auto-loop injects the top semantically-relevant blocks into the prompt so
agents can recall historical decisions long collapsed out of consensus.md. It is
generated automatically; no manual upkeep needed.
- `docs/<role>/` - agent outputs
- `projects/` - all created projects

Expand Down
3 changes: 2 additions & 1 deletion INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
| 守护 | `scripts/wsl/uninstall-wsl-daemon.sh` | 卸载 WSL daemon |
| 守护 | `scripts/wsl/wsl-daemon-status.sh` | 查询 WSL daemon 状态 |
| 守护 | `scripts/macos/install-daemon.sh` | macOS launchd 安装/卸载 |
| 核心 | `scripts/core/auto-loop.sh` | 主循环执行、熔断、日志、共识更新 |
| 核心 | `scripts/core/auto-loop.sh` | 主循环执行、熔断、日志、共识更新、向量记忆召回/沉淀 |
| 记忆 | `scripts/core/memory_vault.py` | 向量记忆库:`index`(沉淀共识+docs)、`search`(语义召回)、`status`、`clear`。纯 Python 零依赖,TF-IDF+余弦 |
| 核心 | `scripts/core/monitor.sh` | 核心状态/日志输出 |
| 核心 | `scripts/core/stop-loop.sh` | 核心停止/暂停/恢复控制 |

Expand Down
4 changes: 4 additions & 0 deletions PROMPT.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

当前共识已预加载在本 prompt 末尾。如果没有,读 `memories/consensus.md`。

> **长程记忆(向量库)**:本轮 prompt 还会附带一段从 `memories/vault/` 语义检索出的"相关历史记忆"(`## Highly-relevant past memory`)。它补充了 consensus 里被折叠掉的历史上下文——包括更早的决策、docs 里的方案细节。当前共识 `.md` 是最新/最权威的,检索出的历史仅作为背景参考,两者冲突时以共识为准。

每轮 cycle 结束后,脚本会把最新的 consensus 快照与 `docs/` 产出分块向量化并存入 `memories/vault/index.json`,供后续周期语义召回。这是自动进行的,agent 无需手动维护 vault。

### 2. 决策

- 有明确 Next Action → 执行它
Expand Down
5 changes: 3 additions & 2 deletions README-ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ daemon (launchd / systemd --user, 崩溃自重启)
└── sleep → 下一轮
```

每个周期是一次独立的 CLI 调用。`memories/consensus.md` 是唯一的跨周期状态——类似接力赛传棒
每个周期是一次独立的 CLI 调用。`memories/consensus.md` 是跨周期运行状态的接力棒。另外系统会自动把 consensus 快照与 `docs/` 产出向量化沉淀到长期记忆库 `memories/vault/`,并在每个周期语义注入相关历史供长期召回(见第 2 层)

## 你该看哪一节(按平台)

Expand Down Expand Up @@ -188,7 +188,8 @@ Auto-Company 并非简单调用 LLM API,而是一个高度解耦的 **多智

### 第 2 层:编排与状态控制层 (Orchestration & State Machine)
* **永续主循环 (The Auto-Loop)**:通过 `scripts/core/auto-loop.sh` 控制的执行循环,让 AI 摆脱“单次对话”,实现 24/7 永续运行。
* **轻量级状态机 (Consensus Memory)**:放弃复杂的向量数据库或内存管理,将跨周期的上下文压缩为一个 Markdown 文件:`memories/consensus.md`。每次循环开始前读取,结束前必须重写,作为整个系统的“接力棒”。
* **轻量级状态机 (Consensus Memory)**:将跨周期的运行上下文压缩为一个 Markdown 文件:`memories/consensus.md`。每次循环开始前读取,结束前必须重写,作为整个系统的“接力棒”。
* **持久化向量记忆 (Long-term Recall)**:在接力棒之上再加一层。`scripts/core/memory_vault.py` 把 consensus 快照与 `docs/` 产出向量化沉淀到 `memories/vault/index.json`(纯 Python 的 TF-IDF + 余弦相似度,零外部依赖、近零成本)。每个新周期自动语义检索最相关的历史知识块注入 prompt,使 consensus 中已被折叠的历史决策与细节依然可召回。存储后端可替换:改 `_embed_chunk()` 即可接上 ChromaDB 或模型 embedding。
* **高可用容错机制 (Resilience & Self-Healing)**:内置熔断器 (连续错误触发冷却)、限流退避 (429 报错自动休眠) 和沙箱重置 (未成功输出有效共识时自动回滚)。

### 第 1 层:基础设施与执行引擎层 (Execution Engine & Infrastructure)
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ daemon (launchd / systemd --user, auto-restart on crash)
└── sleep -> next cycle
```

Each cycle is an independent CLI call. `memories/consensus.md` is the only cross-cycle state.
Each cycle is an independent CLI call. `memories/consensus.md` is the cross-cycle running-state baton. A supplemental persistent **vector memory** (`memories/vault/`) is auto-built from consensus snapshots + `docs/` and injected semantically each cycle for long-term recall (see Layer 2).

## Where To Start (By Platform)

Expand Down Expand Up @@ -187,7 +187,8 @@ Auto-Company is not a simple LLM API wrapper, but a highly decoupled **Multi-Age

### Layer 2: Orchestration & State Machine
* **The Auto-Loop**: The execution loop controlled by `scripts/core/auto-loop.sh` frees the AI from "single-turn conversations", enabling 24/7 continuous operation.
* **Lightweight State Machine (Consensus Memory)**: Forgoes complex vector databases or memory management, compressing cross-cycle context into a single Markdown file: `memories/consensus.md`. Read before every cycle and rewritten before it ends, acting as the system's "baton".
* **Lightweight State Machine (Consensus Memory)**: Compresses cross-cycle running context into a single Markdown file: `memories/consensus.md` (read before every cycle, rewritten before it ends — the "baton").
* **Persistent Vector Memory (Long-term Recall)**: Layered on top of the baton. `scripts/core/memory_vault.py` indexes consensus snapshots + `docs/` outputs into `memories/vault/index.json` (pure-Python TF-IDF + cosine, zero external deps, ~zero cost). Each new cycle auto-retrieves the top semantically-relevant historical blocks and injects them into the prompt, so past decisions and details that consensus.md has collapsed away remain recallable. Swappable backend: replace `_embed_chunk()` to plug in ChromaDB or a model embedding.
* **Resilience & Self-Healing**: Built-in circuit breakers (cooldown triggered by consecutive errors), rate-limit backoff (auto-sleep on 429 errors), and sandbox reset (auto-rollback if a valid consensus is not output).

### Layer 1: Execution Engine & Infrastructure
Expand Down
148 changes: 148 additions & 0 deletions dashboard/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ const els = {
logText: document.getElementById("logText"),
rawText: document.getElementById("rawText"),

vaultSubtitle: document.getElementById("vaultSubtitle"),
vaultStats: document.getElementById("vaultStats"),
vaultChart: document.getElementById("vaultChart"),
vaultLatest: document.getElementById("vaultLatest"),
vaultResults: document.getElementById("vaultResults"),
vaultQuery: document.getElementById("vaultQuery"),
btnVaultSearch: document.getElementById("btnVaultSearch"),
btnVaultRefresh: document.getElementById("btnVaultRefresh"),

btnRefresh: document.getElementById("btnRefresh"),
btnStart: document.getElementById("btnStart"),
btnStop: document.getElementById("btnStop"),
Expand Down Expand Up @@ -236,6 +245,130 @@ async function fetchStatus() {
els.latency.textContent = `Roundtrip: ${elapsed}ms`;
}

function drawVaultChart(bySource) {
const canvas = els.vaultChart;
if (!canvas || !bySource) return;
const ctx = canvas.getContext("2d");
const { width: W, height: H } = canvas;
ctx.clearRect(0, 0, W, H);

const entries = Object.entries(bySource).sort((a, b) => b[1] - a[1]).slice(0, 8);
if (entries.length === 0) {
ctx.fillStyle = "#5b6b7a";
ctx.font = "14px 'Rajdhani', sans-serif";
ctx.fillText("no chunks indexed yet", 16, H / 2);
return;
}

const max = Math.max(...entries.map((e) => e[1]), 1);
const padL = 14, padT = 14;
const rowH = (H - padT * 2) / entries.length;
const barMax = W - padL - 16;

// subtle axes
ctx.strokeStyle = "rgba(255,255,255,0.06)";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(padL, padT - 4);
ctx.lineTo(padL, H - padT);
ctx.moveTo(padL, H - padT);
ctx.lineTo(W - 8, H - padT);
ctx.stroke();

entries.forEach(([src, count], i) => {
const y = padT + i * rowH + rowH / 2;
const len = count / max * barMax;
const grad = ctx.createLinearGradient(padL, 0, padL + len, 0);
grad.addColorStop(0, "#5ee7df");
grad.addColorStop(1, "#7c5cff");
ctx.fillStyle = grad;
const r = 3;
ctx.beginPath();
ctx.roundRect(padL, y - 7, Math.max(len, 2), 14, r);
ctx.fill();

// label
ctx.fillStyle = "rgba(220,230,240,0.85)";
ctx.font = "600 12px 'Rajdhani', sans-serif";
let label = src.replace(/\.md$/i, "");
if (label.length > 18) label = "…" + label.slice(-17);
ctx.fillText(label, padL + Math.max(len, 2) + 8, y + 4);
ctx.fillStyle = "#5ee7df";
ctx.font = "700 12px 'Rajdhani', sans-serif";
ctx.fillText(String(count), W - 10, y + 4);
});
}

function truncateMiddle(text, n = 150) {
const t = String(text || "").replace(/\s+/g, " ").trim();
if (t.length <= n) return t;
return t.slice(0, n) + "…";
}

function renderVault(data) {
if (!data.ok || !data.stats) {
els.vaultSubtitle.textContent = "Vault not initialized yet — will appear after the first cycle.";
els.vaultStats.innerHTML = "<p class='muted'>Waiting for memories/vault/index.json…</p>";
drawVaultChart(null);
els.vaultLatest.innerHTML = "";
return;
}

const s = data.stats;
const lastIdx = data.timestamp ? "" : "";
els.vaultSubtitle.textContent =
`Indexed ${s.lastIndexed || "recently"} · ${s.sizeMb} MB`;

const cols = [
["Chunks", s.chunks],
["Sources", s.sources],
["Distinct Terms", s.distinctTerms],
["Size", `${s.sizeMb} MB`],
];
els.vaultStats.innerHTML = cols
.map(([k, v]) => `<div class="vault-stat"><dt>${k}</dt><dd>${v}</dd></div>`)
.join("");

drawVaultChart(data.bySource || {});

const latest = data.latest || [];
if (latest.length) {
els.vaultLatest.innerHTML = `<div class="vault-latest-title">Latest entries</div>` +
latest.map((c) =>
`<div class="vault-entry"><span class="tag">${escapeHtml(c.source)}</span>` +
`<span class="muted mono">${escapeHtml(truncateMiddle(c.text, 110))}</span></div>`
).join("");
} else {
els.vaultLatest.innerHTML = "";
}
}

function renderVaultSearch(data) {
const hits = (data.search && data.search.hits) || [];
if (!hits.length) {
els.vaultResults.innerHTML = "<p class='muted'>No relevant memory found.</p>";
els.vaultResults.classList.remove("hidden");
return;
}
els.vaultResults.innerHTML =
`<div class="vault-latest-title">Top ${hits.length} semantic matches</div>` +
hits.map((h) =>
`<div class="vault-entry"><span class="tag tag-score">${h.score.toFixed(3)}</span>` +
`<span class="tag">${escapeHtml(h.source)}</span>` +
`<span class="muted mono">${escapeHtml(truncateMiddle(h.text, 140))}</span></div>`
).join("");
els.vaultResults.classList.remove("hidden");
}

async function fetchVault(query) {
const qs = query ? `?q=${encodeURIComponent(query)}&top_k=6` : "?top_k=6";
const res = await fetch(`/api/vault${qs}`, { cache: "no-store" });
const data = await res.json();
renderVault(data);
if (query) renderVaultSearch(data);
return data;
}

async function runAction(action) {
const btn = action === "start" ? els.btnStart : els.btnStop;
const label = btn.textContent;
Expand Down Expand Up @@ -280,8 +413,23 @@ els.btnRaw.addEventListener("click", () => {
els.autoToggle.addEventListener("change", resetAutoTimer);
els.refreshInterval.addEventListener("change", resetAutoTimer);

els.btnVaultRefresh.addEventListener("click", () => {
fetchVault().catch(() => {});
});
els.btnVaultSearch.addEventListener("click", () => {
const q = els.vaultQuery.value.trim();
if (q) fetchVault(q).catch(() => {});
});
els.vaultQuery.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
const q = els.vaultQuery.value.trim();
if (q) fetchVault(q).catch(() => {});
}
});

fetchStatus().catch((err) => {
const msg = err instanceof Error ? err.message : String(err);
els.rawText.textContent = msg;
});
fetchVault().catch(() => {});
resetAutoTimer();
23 changes: 23 additions & 0 deletions dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,29 @@ <h3>Consensus Head</h3>
</article>
</section>

<section class="panel reveal-5 panel-vault">
<div class="panel-head panel-head-wrap">
<div>
<h3>Memory Vault</h3>
<p class="muted" id="vaultSubtitle">Persistent vector memory index</p>
</div>
<div class="vault-controls">
<input id="vaultQuery" class="vault-input" type="text"
placeholder="Semantic search memory… (e.g. pricing, launch)" />
<button id="btnVaultSearch" class="btn btn-ghost small">Search</button>
<button id="btnVaultRefresh" class="btn btn-ghost small">Refresh</button>
</div>
</div>
<div class="vault-row">
<div class="vault-stats" id="vaultStats"></div>
<div class="vault-bar-wrap">
<canvas id="vaultChart" class="vault-chart" width="520" height="180"></canvas>
</div>
</div>
<div id="vaultLatest" class="vault-latest"></div>
<div id="vaultResults" class="vault-results hidden"></div>
</section>

<section class="panel reveal-5">
<div class="panel-head">
<h3>Recent Log</h3>
Expand Down
Loading