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
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ curl -fsSL https://raw.githubusercontent.com/kachofugetsu09/akashic-agent/main/s
```

安装器会显示 current/target identity 并等待确认;无人值守时加 `--yes`。只准备镜像、Bridge venv、
manifest、unit 和稳定 CLI 而不启动服务时加 `--no-activate`。首次激活前,
manifest 而不改变运行单元、CLI 和 state 时加 `--no-activate`。首次激活前,
`/srv/data/services/akashic/state/config.toml`、`workspace/` 和 `plugin-home/` 必须已经由 operator
准备好;没有现成配置时从 `config.example.toml` 复制后按目标机编辑,不能把测试配置或假凭据带入正式
state。若配置没有 OpenCode Go 凭据,安装进程还必须从受保护的环境变量取得
Expand All @@ -206,11 +206,12 @@ exact commit
模板和 identity 校验使用,不是 Core 的业务插件搜索路径。`build_host_runtime_release.py --legacy-checkout`
只保留给旧开发兼容,不能用于正式发行,也不能让镜像通过 checkout 自动装配插件。

安装后用稳定 CLI 核对实际身份或恢复上一代软件:
日常升级默认只更新 Core/Bridge,不备份、不自动更新插件;`--backup` 才开启部署备份。
插件和迁移用 `--plan` 明确列出,待迁移未批准时在停机前失败。完整命令、清单示例及恢复步骤见
[部署操作手册](docs/design/operator-deployment.md)。安装后核对实际身份:

```bash
akashic-release doctor
akashic-release rollback --yes
```

`runtime.env` 由激活事务原子生成,至少闭合 `AKASHIC_RUNTIME_COMMIT`、
Expand All @@ -224,8 +225,9 @@ generation 字段;需要更新时重新准备并激活一个完整 release。
manifest 和 stable artifact,不重新安装、启用默认 profile 或覆盖插件配置;因此 operator 后续禁用、卸载
或用不同名称的 provider 替换插件后,重启仍保持当前组合。卸载走正在运行的 Core 控制面,例如
`python main.py plugin-uninstall <plugin-id> --config PATH --workspace PATH`;普通卸载保留该插件的
`plugin-data`。要恢复软件代际使用 `akashic-release rollback --yes`,它恢复 runtime/env 和服务身份,
不回滚已经写入 Workspace 的业务数据或外部效果。
`plugin-data`。部署在停止期失败时保留现场;已经记录完整发布结果的尝试用
`akashic-release resume --attempt <deploy-receipt>` 只重试启动验收。是否恢复数据或降级由部署者决定,
工具不自动回退旧 image,也不把服务启动当成业务数据或外部效果已回滚。

发布验收还必须单独证明 Core-only 启停。下面的命令把分发制品写到仓库外;runner 会先从 `core.tar`
启动并停止无业务源码的 Core,再安装 bundle 组合。检查报告中的 `core_bootstrap.status` 与 stop 证据;
Expand Down
39 changes: 32 additions & 7 deletions agent/migrations/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class MigrationOutcome:


class MigrationRunner:
"""在 runtime 启动前执行缺失的 Yoyo 迁移。"""
"""核对迁移账本,在明确初始化或部署授权下执行缺失 step。"""

def __init__(
self,
Expand Down Expand Up @@ -80,37 +80,46 @@ def __init__(
).expanduser()

def run(self) -> MigrationOutcome:
"""执行当前目录并返回本次落账的迁移 ID。"""
"""既有选择只检查迁移;显式首次初始化保留建库流程。"""

# 1. 复用 workspace 锁串行化迁移与 runtime 启动
workspace_lock = WorkspaceInstanceLock(self.workspace)
workspace_lock.acquire()
try:
if self.startup_selection:
self.fixed_sources = _startup_sources(self.workspace)
return self._apply_pending()
return self._apply_pending(
approved_migrations=() if self.startup_selection and PluginSelection(self.workspace).read() is not None else None,
)
finally:
workspace_lock.release()

def run_under_maintenance(
self, maintenance: WorkspaceMaintenanceLock, *, core_only: bool = False,
approved_migrations: tuple[str, ...] = (),
) -> MigrationOutcome:
"""Apply migrations while the release owns the workspace maintenance lock."""

if (maintenance.paths != (
self.workspace / ".supervisor.lock", self.workspace / ".instance.lock",
) or len(maintenance._streams) != 2):
raise RuntimeError("release migration 缺少当前 workspace maintenance owner")
return self._apply_pending(core_only=core_only)
return self._apply_pending(core_only=core_only, approved_migrations=approved_migrations)

def check(self, approved_migrations: tuple[str, ...] = ()) -> tuple[str, ...]:
"""只读核对目标代码的待迁移集合,不创建账本或运行 step。"""
return self._apply_pending(approved_migrations=approved_migrations, check_only=True).migrations

def _apply_pending(self, *, core_only: bool = False) -> MigrationOutcome:
def _apply_pending(
self, *, core_only: bool = False,
approved_migrations: tuple[str, ...] | None = None, check_only: bool = False,
) -> MigrationOutcome:
"""加载不可变目录并提交全部缺失迁移。"""

# 1. 初始化由 workspace 持有的迁移账本
fresh = _workspace_is_empty(self.workspace, self.config_path)
baseline = _read_baseline(self.ledger_path)
try:
self.ledger_path.parent.mkdir(parents=True, exist_ok=True)
core_migrations = _read_migrations(str(self.migrations_root))
bundles = discover_migration_bundles(
plugin_dirs=self.plugin_dirs,
Expand Down Expand Up @@ -143,8 +152,24 @@ def _apply_pending(self, *, core_only: bool = False) -> MigrationOutcome:
applied_ids=applied_ids,
bundles=bundles,
)
# 既有 workspace 的迁移必须来自部署者清单;先核对再打开写连接。
loaded = set(core_ids + bundle_ids)
pending_ids = loaded - set(applied_ids) - set(baseline or ())
if approved_migrations is not None:
unknown = set(approved_migrations) - loaded - set(applied_ids)
missing = pending_ids - set(approved_migrations)
if unknown or missing:
raise RuntimeError(
f"迁移清单不匹配: 未批准={sorted(missing)}, 未知={sorted(unknown)}"
)
if check_only:
return MigrationOutcome(state="current", migrations=tuple(sorted(pending_ids)))
if fresh and baseline is not None:
self.ledger_path.parent.mkdir(parents=True, exist_ok=True)
_save_baseline(self.ledger_path, baseline)
if not pending_ids:
return MigrationOutcome(state="current")
self.ledger_path.parent.mkdir(parents=True, exist_ok=True)
backend = get_backend(self._ledger_uri())
os.chmod(self.ledger_path, 0o600)

Expand Down Expand Up @@ -333,7 +358,7 @@ def _read_applied_ids(path: Path) -> tuple[str, ...]:

if not path.exists():
return ()
connection = sqlite3.connect(path)
connection = sqlite3.connect(f"file:{quote(path.as_posix(), safe='/')}?mode=ro", uri=True)
try:
try:
rows = connection.execute(
Expand Down
1 change: 1 addition & 0 deletions agent/plugin_composition/message_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def session_row(entry: SessionEntry) -> dict[str, object]:
"message_count": entry.message_count,
"head_seq": entry.head_seq,
"first_message_content": text,
"scope": dict(entry.attributes.scope),
}


Expand Down
33 changes: 33 additions & 0 deletions agent/plugin_composition/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,46 @@ class SessionAdmission:

def __init__(self, log: _MessageLog | None):
self._log = log
self._dimensions: dict[str, tuple[Context, Callable[[str], None]]] = {}

# 每个 scope 维度只有一个 owner 负责校验取值;卸载只释放内存注册。
async def register_dimension(
self, ctx: Context, *, name: str, check: Callable[[str], None],
) -> Effect:
if ctx.require(SESSION_ADMISSION) is not self:
raise PermissionError("维度注册不属于当前 SessionAdmission")
_ = SessionAttributes(scope=((name, "probe"),))
def setup():
if name in self._dimensions:
raise ValueError(f"Session 维度已有 owner: {name}")
self._dimensions[name] = (ctx, check)
def cleanup() -> None:
del self._dimensions[name]
return cleanup
return await ctx.effect(setup, label="session-dimension:" + name)

def ensure(self, ctx: Context, session_id: str, attributes: SessionAttributes) -> SessionAttributes:
if self._log is None:
raise RuntimeError("candidate 验证期禁止接纳正式 Session")
_ = ctx.require_runtime_owner(SESSION_ADMISSION, self)
# 1. 维度值只在首次接纳时由其 owner 校验;已有 Session 只比较固定事实。
if attributes.scope and not self._admitted(session_id):
for name, value in attributes.scope:
grant = self._dimensions.get(name)
if grant is None:
raise PermissionError(f"Session 维度没有 owner: {name}")
grant[1](value)
# 2. 固定事实写入与冲突检查仍由同一个 create-once 事务完成。
return self._log.ensure_session(session_id, attributes)

def _admitted(self, session_id: str) -> bool:
assert self._log is not None
try:
_ = self._log.catalog().attributes(session_id)
except ValueError:
return False
return True


MESSAGE_WRITERS = ServiceKey[MessageWriters]("core.message_writers")
OWNER_STATE = ServiceKey[OwnerState]("core.owner_state")
Expand Down
21 changes: 3 additions & 18 deletions docker/host-runtime/distribution-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,13 @@ test -r /opt/akashic/runtime-info.json
--expected-commit "$EXPECTED_COMMIT" \
--expected-tree "$EXPECTED_TREE"

if [[ "${1:-}" == "adopt-bundled" ]]; then
if [[ "${1:-}" == "publish" ]]; then
shift
exec /opt/venv/bin/python /opt/akashic/source/scripts/install_plugin_distribution.py \
--distribution /opt/akashic/distribution \
--profile /opt/akashic/distribution/profiles/default.json \
--workspace "$WORKSPACE" \
--plugins-home "$PLUGIN_HOME" \
--config "$CONFIG" \
--receipt "$WORKSPACE/runtime/distribution-install.json" \
--adopt-bundled "$@"
fi

if [[ "${1:-}" == "upgrade-bundled" ]]; then
shift
exec /opt/venv/bin/python /opt/akashic/source/scripts/install_plugin_distribution.py \
--distribution /opt/akashic/distribution \
--profile /opt/akashic/distribution/profiles/default.json \
--workspace "$WORKSPACE" \
--plugins-home "$PLUGIN_HOME" \
--config "$CONFIG" \
--receipt "$WORKSPACE/runtime/distribution-install.json" \
--upgrade-bundled "$@"
--workspace "$WORKSPACE" --plugins-home "$PLUGIN_HOME" --config "$CONFIG" \
--publish "$@"
fi

mkdir -p "$WORKSPACE" "$PLUGIN_HOME"
Expand Down
4 changes: 3 additions & 1 deletion docs/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@
| 任何会修改仓库文件的任务 | 本索引 → [`WORKFLOW.md`](WORKFLOW.md) → 下方对应领域 | 当前分支、目标分支、完整 diff、验证报告 |
| Web/Mobile 共享 Session、Akashic Channel 或客户端 adapter | `projectneed` AKC-001~AKC-003 → [0044](decisions/0044-akashic-channel-uses-web-and-mobile-adapters.md) → [0067](decisions/0067-clients-are-ordinary-plugin.md) → [Akashic Channel 与客户端 Adapter 规格](design/akashic-channel-client-adapters.md) → [持久化状态地图](design/persistence-state-map.md) | `session/`、`agent/plugin_composition/channels.py`、`plugins/akashic_clients/`、`bootstrap/app.py`、`bootstrap/chat_api.py`、`plugins/scheduler/`、`plugins/wake/`、`plugins/akasha/`;实现仍需独立授权 |
| V3 durable inbound 与 host boot identity | `projectneed` RUN-003 → [V3 durable inbound 与 host boot identity 合同](design/plugin-v3-durable-inbound-host-contract.md) → [持久化状态地图](design/persistence-state-map.md) | `agent/plugin_composition/channels.py`、`plugins/channels/provider.py`、`bus/queue.py`、`session/inbound_store.py`、各客户端 adapter;旧 Mobile consumer 迁移前不得恢复旧 facade |
| 其他 Akashic 产品路线、Project Session、Project Akasha 或大型 tool result | [未来方向与 Issue 拆分草案](design/akashic-future-roadmap-issue-drafts.md) → 草案中对应领域的现行条款、决策与设计 | `session/`、`plugins/eventmail/`、`plugins/wake/`、`plugins/drift/`、`plugins/scheduler/`、`plugins/subagent/`、`plugins/akasha/`、`plugins/message_push/`;草案未提升为现行合同前不得直接实现 |
| Project、Session scope 宽键、Akasha 独立图与记忆策略 | `projectneed` SES-010、MEM-009、MEM-013 → [0073](decisions/0073-session-scope-routes-akasha-graphs.md) → [持久化状态地图](design/persistence-state-map.md) | `session/log.py`、`agent/plugin_composition/messages.py`、`plugins/projects/`、`plugins/conversation/`、`plugins/akasha/scopes.py`、`plugins/akasha/plugin.py`、`frontend/chat/src` |
| 其他 Akashic 产品路线、Project working root / coding context 或大型 tool result | [未来方向与 Issue 拆分草案](design/akashic-future-roadmap-issue-drafts.md) → 草案中对应领域的现行条款、决策与设计 | `session/`、`plugins/eventmail/`、`plugins/wake/`、`plugins/drift/`、`plugins/scheduler/`、`plugins/subagent/`、`plugins/akasha/`、`plugins/message_push/`;草案未提升为现行合同前不得直接实现 |
| Prompt、人格、上下文窗口、历史裁切、重试 | `projectneed` 第 5~7、13 节 → [Veda 人格设计](design/veda-persona.md) → [0002](decisions/0002-context-reduction-is-a-nondestructive-projection.md) → [0030](decisions/0030-session-context-compaction-ledger.md) → [Session compaction ledger](design/session-context-compaction-ledger.md) → [上下文事故设计](design/project-workbook-and-semantic-safety.md) → [Wake 最近主动消息上下文](design/wake-recent-delivery-context.md) | `plugins/context/`、`plugins/compaction/`、`plugins/markdown_memory/`、`agent/prompting/`、`session/`、`plugins/turn_projection/` |
| 旧工具效果缺少恢复回执 | [0061](decisions/0061-archive-stopped-legacy-executions.md) → [持久化状态地图](design/persistence-state-map.md) | `agent/migrations/turn_messages.py` |
| 插件 Message metadata、Citation/Meme 附加信息 | `projectneed` SES-009 → [0060](decisions/0060-message-plugin-metadata.md) → [Message metadata 合同](design/0902-reviewed-v4.md#34-message-metadata-的实现与迁移) → [持久化状态地图](design/persistence-state-map.md) | `session/message.py`、`session/log.py`、`plugins/content/`、`plugins/react/`、`infra/channels/message_view.py` |
Expand Down Expand Up @@ -143,6 +144,7 @@
| 工具引用、工具组、搜索展示与来源工具范围 | [0062](decisions/0062-tools-flow-through-provider-views.md) → [插件工具引用与模型展示设计](design/plugin-tool-view.md) → [持久化状态地图](design/persistence-state-map.md) | `plugins/tools/`、`plugins/tool_search/`、`plugins/conversation/`、`plugins/reply/`、`plugins/react/`、`plugins/wake/`、`plugins/scheduler/`、`plugins/subagent/`、`agent/plugin_composition/mcp_slots.py` |
| 插件 v3 inbound/outbound channel capability | [插件 v3 Channel capability 合同](design/plugin-v3-channel-capability-task-contract.md) → [插件 v3 Channel 附件持久化合同](design/plugin-v3-channel-attachment-task-contract.md) → [插件 v3 committed command catalog 合同](design/plugin-v3-command-catalog-task-contract.md) | `agent/plugin_composition/channels.py`、`agent/plugins/manager.py`、`infra/channels/`、`plugins/akashic_clients/`、`plugins/sources/`、`plugins/delivery/`、`plugins/delivery_policy/`、`bootstrap/chat_api.py`、`bootstrap/app.py`、`session/` |
| 移动端查看 Markdown、定时任务、插件、Skill、MCP | `projectneed` 第 6、10~13 节 → [移动端运行时检查](design/mobile-runtime-inspection.md) → [v3 Mobile UI/query capability](design/plugin-v3-mobile-ui-query-task-contract.md) → [持久化状态地图](design/persistence-state-map.md) | `plugins/akashic_clients/mobile_realtime/runtime_inspection.py`、`plugins/akashic_clients/mobile_realtime/protocol.py`、`plugins/akashic_clients/mobile_realtime/channel.py`、`agent/plugins/mobile_ui.py` |
| 部署、升级、可选备份、失败重试 | [0074](decisions/0074-deployment-policy-belongs-to-operator.md) → [部署操作手册](design/operator-deployment.md) → [Core/Bridge 安装设计](design/akashic-core-bridge-installer.md) | `scripts/install-akashic.sh`、`scripts/akashic_release/`、`scripts/install_plugin_distribution.py` |
| Workspace、配置、凭据、迁移、备份 | `projectneed` 第 6、11~13 节 → [持久化状态地图](design/persistence-state-map.md) → [0066](decisions/0066-yoyo-current-baseline.md) → [Yoyo 迁移维护手册](design/git-migration-authoring.md) | `main.py`、`bootstrap/init_workspace.py`、`agent/config.py`、`agent/migrations/`、`migrations/core/`、`agent/model_runtime/auth/store.py`、`scripts/rolling_backup.py` |
| 高风险 refactor、语义不变重构、CI oracle | `projectneed` 第 4~6、13、15 节 → [正交化测试基线](refactor/orthogonality-test-baseline.md) → [综合重构账本](refactor/clean-code-ledger.md) → [上下文事故设计](design/project-workbook-and-semantic-safety.md) → 相关决策 | 改动前后的完整 diff、概念基线 pytest、write set |
| 正交化测试基线、跨仓库插件契约 | `projectneed` 第 10、13、15 节 → [正交化测试基线](refactor/orthogonality-test-baseline.md) → [0004](decisions/0004-cross-repository-evidence-is-an-immutable-combination.md) → [移动端与跨仓库 Gate](design/mobile-cross-repository-semantic-gate.md) → [Gate 总体设计](spark/2026-07-16-change-impact-contract-gate.md)(change-impact 已退役) → [测试与 Gate 清理账本](refactor/test-gate-cleanup-ledger.md)(已被基线文档取代) → [持久化状态地图](design/persistence-state-map.md) | `tests/`、`private_runtime/` |
Expand Down
4 changes: 2 additions & 2 deletions docs/NOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ Session/Message 全身份迁移、配置、Akasha 和 Android 强制全量同步
不改 lock 指向任意 HEAD、删 case 或降低验收覆盖取绿。
- **真实资源验收**:本地 Workload 协议测试不代表 Docker 写入挂载、跨 boot 资源恢复
或显示代理已验证;这些结果须由对应真实 Controller 与客户端提供。
- **发行与恢复**:已有 state 的 `akashic-release install` 升级路径已接入停止期备份、目标
Core/插件 Yoyo、完整 selection 与 live Fiber 核对;远端 CI、正式发布、真实运行数据的隔离
- **发行与恢复**:已有 state 的 `akashic-release install` 按[部署操作手册](design/operator-deployment.md)
使用显式插件/迁移清单、可选备份、完整 selection 与 live Fiber 核对;远端 CI、正式发布、真实运行数据的隔离
恢复演练和 final enable 仍需独立交付。普通 restart/ensure_profile 不自动采用新 bundle;
通用 distribution 夹具不能替代真实 Akasha 发行输入、重放完成及功能验收,历史未决资源
也不能用源码通过代为结算。
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# 0021 · Yoyo workspace 账本定义迁移原点
> 2026-09-25 勘误:[0074](0074-deployment-policy-belongs-to-operator.md) 将既有 selected Root 的普通启动改为迁移检查;执行与备份由部署者显式选择。Yoyo 成功账本、首次初始化及已发布 step 合同保留。


- 状态:accepted
- 日期:2026-08-02
Expand Down
Loading
Loading