Skip to content
Draft
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: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ DeepSeek/deepseek-v4-flash

MA can run Agora as a provider-owned MCP stdio subprocess instead of asking users to manage a local HTTP server. The TUI reports real provider stages such as local-model loading, memory mounting, and generation.

When the active provider is Agora, users operate uniquely named, independently versioned Memories; MemoryPatch is the immutable version object. MA reports `mounted` only after a later Agora response returns the requested ordered Patch ids and an advanced PatchSet revision. It never fakes memory by injecting facts into a prompt.
When the active provider is Agora, users operate uniquely named, independently versioned Memories; MemoryPatch is the immutable version object. MA reports `mounted` only after a later Agora response returns the requested ordered Patch ids and an advanced PatchSet revision. Memory management stays in the host-side `/memory` control plane: MA neither injects facts into the prompt nor exposes MemoryPatch policy and management tools to the conversational model.

`/memory` can mount zero or more Memories per project or conversation and hot-swap them at the next request boundary without restarting the base model. One intake batch may mix a new Memory with increments to multiple existing Memories; the source is extracted once and each target reports completed/noop/review/conflict/failed independently. Automatic intake requires explicit targets and runs after 4 new user turns or about 2,000 pending tokens plus 60 seconds of idle time. Failed targets can be retried alone or explicitly abandoned without blocking input or adding transcript noise.

Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ DeepSeek/deepseek-v4-flash

MA 可以把 Agora 作为 provider 自己管理的 MCP stdio 子进程运行,不要求用户维护本地 HTTP 服务。TUI 会展示真实的本地模型加载、记忆挂载和生成阶段。

当 Agora 是当前 provider 时,用户操作的是名称唯一、可持续迭代的 Memory;MemoryPatch 是它的不可变版本。只有下一次 Agora 响应 metadata 返回相同的 ordered Patch ids 和更新后的 PatchSet revision,MA 才会显示 `mounted`;不会把一段事实塞进 prompt 伪装成记忆
当 Agora 是当前 provider 时,用户操作的是名称唯一、可持续迭代的 Memory;MemoryPatch 是它的不可变版本。只有下一次 Agora 响应 metadata 返回相同的 ordered Patch ids 和更新后的 PatchSet revision,MA 才会显示 `mounted`。记忆管理只存在于宿主侧 `/memory` 控制面:MA 既不会把事实塞进 prompt,也不会向对话模型暴露 MemoryPatch 策略和管理工具

`/memory` 可在项目或单个会话中同时挂载 0~N 个 Memory,并在下一次请求边界热拔插,不重启基座。内化时可以在一个 batch 中混合“新建 Memory”和“增量到多个旧 Memory”;同一 source 只提取一次,各目标独立报告 completed/noop/review/conflict/failed。自动内化必须显式选择目标,默认在 4 个新增用户回合或约 2000 pending tokens、空闲 60 秒后运行;失败目标可单独重试或明确放弃,输入框和 transcript 不受影响。

Expand Down
1 change: 1 addition & 0 deletions docs/agora-vip-provider-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ MA 将 Agora 作为内部 VIP provider 适配:Agora 不再只是一个 OpenAI-
4. MemoryPatch 状态
- Session meta 保留 `providerState`,字段包括 `provider_id`、`agora_session_id`、`memory.status`、`profile_id`、`binding_id`、`active_memory_patch_ids`、`last_verified_at`。
- MA 只能从 Agora `chat_complete` 响应 metadata 更新 mounted 证据。
- Memory 控制策略、Patch 状态和管理工具只属于宿主 Controller/TUI,不得进入普通对话的 system prompt、tool schema 或 transcript。
- mount、disable、internalize、rollback 必须验证下一次 `chat_complete` metadata 后才报告成功。
- `/memory` 以具名 Memory 为第一层,提供多 Memory 挂载、新建/重命名、混合多目标内化、显式自动目标、历史和 CAS 回滚;Profile 仅作为后台 binding 实现。
- 自动 intake 一次提交显式目标列表;completed/noop 不重复,review/conflict/failed 只重试未完成目标;后台任务不得禁用输入框或写入 transcript。
Expand Down
13 changes: 7 additions & 6 deletions docs/prd-ma-agora-memory-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Agora v2 已提供:

- 不在 MA 重新实现 Memory 编译、gate、segment、CAS 或回滚算法。
- 不把记忆事实注入 system prompt 模拟 Agora Memory。
- 不把 MemoryPatch 管理策略、状态或工具 schema 暴露给普通对话模型。
- 不支持生成到一半的 mid-token 热插拔;切换在下一次 chat 边界生效。
- 不支持跨不同 base model 挂载不兼容 Patch。
- 不引入账号、登录、机器绑定、许可证、云同步或 ZimoOS 依赖。
Expand Down Expand Up @@ -250,7 +251,7 @@ conversation override > project default > user default > no memory

### 9.5 手动内化

入口:`i`、`/memory internalize` 或 agent memory tool
入口:`i`、`/memory internalize` 或 Memory Console

步骤:

Expand Down Expand Up @@ -331,7 +332,7 @@ conversation override > project default > user default > no memory
| `/memory status` | 查看 ids、binding、batch、revision 和 capability 详情 |
| `/memory disable` | 当前 scope 不使用记忆 |

命令、TUI 和 agent tool 必须调用同一个 Controller,禁止三套业务逻辑
命令和 TUI 必须调用同一个宿主 Controller,禁止重复业务逻辑;Controller 不作为对话模型工具暴露

## 11. MA 状态模型

Expand Down Expand Up @@ -381,7 +382,7 @@ type AgoraMemoryV2State = {

## 12. Controller 改造

现有 `AgoraMemoryController` 升级为 v2,TUI、命令和 agent tool 共同复用
现有 `AgoraMemoryController` 升级为 v2,TUI 和命令在宿主控制面共同复用

```text
capabilities()
Expand Down Expand Up @@ -577,7 +578,7 @@ v2 必需 capability:
## 20. 关闭标准

- MA 不再以 `writable_patch_family` 作为 Memory Console 主交互。
- TUI、命令、agent tool 全部复用同一个 v2 Controller。
- TUI 和命令全部复用同一个 v2 Controller,普通模型请求不包含该 Controller 的工具 schema
- 具名 Memory、多挂载、多目标内化、自动目标、回滚、revision 验证全部可用。
- 所有挂载成功都有真实 chat metadata 证据。
- Patch-only 切换不重启 Agora、不显示模型重新加载。
Expand Down Expand Up @@ -616,7 +617,7 @@ Agora v2 当前证据:
- `providerState` 持久化框架。
- StatusBar 第一行 Context Usage。
- Memory Console 的 modal、键盘导航和 Activity 区域。
- 命令注册和 agent tool 暴露框架
- 命令注册和宿主 Controller 调用框架
- 现有 PTY、provider runtime、普通 Provider 和 Context Usage 测试基础。

### 22.2 必须替换
Expand Down Expand Up @@ -652,7 +653,7 @@ Agora v2 当前证据:
- [ ] 名称冲突保留输入并允许原地重试。
- [ ] StatusBar 显示 Memory 名称、版本、+N、verified/pending/stale 和 Activity。
- [ ] 实现 mounted badge 流动高光及 NO_COLOR/reduced-motion 降级。
- [ ] 重写 `/memory` 命令并确保与 TUI、agent tool 共用 Controller。
- [ ] 重写 `/memory` 命令并确保与 TUI 共用宿主 Controller,且不进入模型工具列表

### Automatic intake

Expand Down
13 changes: 9 additions & 4 deletions scripts/package-portable.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,21 @@ function copyAgoraRuntime(appDir, target) {
const manifestPath = path.join(source, 'manifest.json');
const binaryPath = path.join(source, 'bin', 'agora');
if (!fs.existsSync(manifestPath) || !fs.existsSync(binaryPath)) {
throw new Error('macos-arm64 portable release requires MA_AGORA_ARTIFACT_DIR with Agora 0.2.0 native artifact');
throw new Error(`macos-arm64 portable release requires MA_AGORA_ARTIFACT_DIR with Agora ${agoraLock.version} native artifact`);
}
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
if (agoraLock.published !== true) {
throw new Error('macos-arm64 portable release requires a published, notarized Agora release lock');
}
if (manifest.version !== '0.2.0' || manifest.host_protocol_major !== 1) {
if (
manifest.version !== agoraLock.version ||
manifest.host_protocol_major !== agoraLock.host_protocol_major ||
manifest.native_core_abi !== agoraLock.native_core_abi ||
manifest.runtime_layout !== agoraLock.runtime_layout
) {
throw new Error(`unexpected Agora contract: ${manifest.version}/host-v${manifest.host_protocol_major}`);
}
const requiredCapabilities = ['mcp-stdio', 'memory-profile-v2', 'memory-intake-v2'];
const requiredCapabilities = agoraLock.capabilities ?? [];
if (!requiredCapabilities.every((capability) => manifest.capabilities?.includes(capability))) {
throw new Error('Agora artifact is missing required Memory v2 capabilities');
}
Expand Down Expand Up @@ -86,7 +91,7 @@ function copyAgoraRuntime(appDir, target) {
fs.writeFileSync(
path.join(destination, 'runtime-lock.json'),
JSON.stringify({
version: '0.2.0',
version: agoraLock.version,
package: '@zimoos/agora-darwin-arm64',
platform: 'darwin-arm64',
host_protocol_major: 1,
Expand Down
53 changes: 50 additions & 3 deletions scripts/sync-agora-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,21 @@ import * as path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const VERSION = '0.2.0';
const packageMetadata = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
const VERSION = packageMetadata.optionalDependencies?.['@zimoos/agora'];
const PACKAGE_NAMES = ['@zimoos/agora', '@zimoos/agora-darwin-arm64'];
const REQUIRED_TAMPER_CHECKS = [
'runtime_hash_tamper',
'unmanifested_file',
'missing_manifest',
'protocol_mismatch',
'dependency_adhoc_resign',
'adhoc_resign',
];

if (typeof VERSION !== 'string' || !/^\d+\.\d+\.\d+$/.test(VERSION)) {
throw new Error('Agora release evidence rejected: package.json must pin an exact @zimoos/agora semver');
}

function fail(message) {
throw new Error(`Agora release evidence rejected: ${message}`);
Expand All @@ -21,6 +34,34 @@ export function buildReleaseLock(evidence) {
if (!/^[a-f0-9]{64}$/.test(String(evidence.manifest_sha256 ?? ''))) {
fail('manifest_sha256 is missing or malformed');
}
const artifactAudit = evidence?.artifact_audit;
if (
artifactAudit?.passed !== true ||
artifactAudit?.readable_language_source_files !== 0 ||
artifactAudit?.private_keys !== 0 ||
artifactAudit?.repository_source_paths !== 0 ||
artifactAudit?.debug_symbols !== 0
) {
fail('artifact source/private-key/source-path/debug audit did not pass');
}
const tamperChecks = new Map((evidence?.tamper_audit?.checks ?? []).map((item) => [item?.name, item]));
if (
evidence?.tamper_audit?.passed !== true ||
REQUIRED_TAMPER_CHECKS.some((name) => tamperChecks.get(name)?.exit_code !== 126)
) {
fail('required launcher tamper checks did not fail closed');
}
const startup = evidence?.startup_performance;
if (
startup?.passed !== true ||
startup?.cold?.runs < 5 ||
startup?.warm?.runs < 30 ||
startup?.cold?.p95_ms > 2000 ||
startup?.warm?.p95_ms > 1000 ||
startup?.precondition !== 'npm postinstall packaged doctor completed'
) {
fail('packaged Agora startup performance evidence is missing or below the Issue #42 gate');
}
const signatures = Object.values(evidence.signatures ?? {});
if (signatures.length < 5 || signatures.some((item) => (
!String(item?.authority ?? '').startsWith('Developer ID Application:') || !String(item?.team_id ?? '')
Expand All @@ -33,7 +74,7 @@ export function buildReleaseLock(evidence) {
if (item?.version !== VERSION || !String(item?.integrity ?? '').startsWith('sha512-')) {
fail(`missing exact npm integrity for ${name}@${VERSION}`);
}
const forbidden = (item.files ?? []).filter((file) => /\.(py|pyc|js|map|pem|key|p12)$/i.test(file));
const forbidden = (item.files ?? []).filter((file) => /\.(py|pyc|pyi|js|jsx|mjs|cjs|ts|tsx|map|key|p12)$/i.test(file));
if (forbidden.length > 0) fail(`${name} contains forbidden files: ${forbidden.join(', ')}`);
}
const teamIds = new Set(signatures.map((item) => item.team_id));
Expand All @@ -46,7 +87,13 @@ export function buildReleaseLock(evidence) {
published: true,
notarization_id: evidence.notarization.id,
manifest_sha256: evidence.manifest_sha256,
capabilities: ['mcp-stdio', 'memory-profile-v2', 'memory-intake-v2'],
startup_performance: {
cold_p95_ms: startup.cold.p95_ms,
warm_p95_ms: startup.warm.p95_ms,
},
tamper_checks: REQUIRED_TAMPER_CHECKS,
runtime_layout: 'nuitka-standalone-v1',
capabilities: ['mcp-stdio', 'memory-profile-v2', 'memory-intake-v2', 'memory-lineage-cas-v1'],
packages: Object.fromEntries(PACKAGE_NAMES.map((name) => [name, { integrity: packages[name].integrity }])),
};
}
Expand Down
Loading