Workspace resource layout decision¶
Status: implemented.
The canonical workspace stores each Agent definition in one TOML file and
each subagent definition in one Markdown file. The logical resource identities
are agent:<name> and subagent:<name>. Memory and inbox notes are files,
skills are directories, MCP definitions are files, and project resources are
grouped under each project directory. Collection fields in skills.toml and
project configuration are lists.
Canonical files¶
An Agent lives at agents/<name>.toml. Each file contains exactly one existing
[agents.<name>] table, including its nested capability tables. The filename
and table name must match. Keeping the current table schema allows the bundled
templates/agents/<name>.toml fragments to serve as workspace files without
rewriting their fields.
# agents/codex.toml
[agents.codex]
display_name = "Codex"
instruction_path = ".codex/AGENTS.md"
[agents.codex.runner]
command = ["codex", "-C", "{workdir}", "{prompt}"]
A subagent lives at subagents/<name>.md. Its filename supplies its name; YAML
frontmatter contains description, a nonempty agents list, and optional
platform tables. The Markdown body contains the instructions. Platform tables
use inline JSON objects where nested values are needed, so their TOML values
round-trip without a YAML dependency. The parser rejects duplicate keys,
unknown fields, invalid types, and unsupported YAML features.
---
description: "Review code changes"
agents: ["codex", "claude-code"]
codex: {"model": "o3"}
---
Review the changes and report findings.
The existing subagents.toml is read only by the migration command. New
subagents write their metadata into Markdown frontmatter. Editing
instructions preserves frontmatter; changing metadata preserves the body and
unrecognized Markdown content below the closing delimiter.
Required migration gate¶
Use a Git-tracked layout.toml with version = 2 as the completion marker.
New initialization writes the marker and only the new layout. A workspace
without this marker, with an unsupported marker version, or with legacy files
still present is unavailable to normal commands. Runtime loaders read only
agents/*.toml and subagent Markdown frontmatter; they contain no fallback for
agents.toml or subagents.toml.
The installation step does not modify a user workspace. On the first normal command targeting a legacy workspace, the CLI exits before reading or writing runtime resources and prints the workspace path plus the exact command:
This workspace needs migration. Run:
aikito migrate workspace-resources --dry-run
aikito migrate workspace-resources
Help, version, workspace path discovery, fresh workspace initialization, and the migration command remain available. Connecting or reinitializing an existing legacy workspace is blocked with the same guidance. A migration that was interrupted also keeps normal commands blocked until recovery completes. This is an explicit one-time upgrade, with no compatibility adapter in normal operation and no automatic migration during package installation or sync.
Migration command¶
The dedicated aikito migrate workspace-resources --dry-run command previews
the change; without --dry-run, it applies the change under the workspace
writer lock and shared transaction journal. A preview reports every file to
create or remove, unsupported names, and collisions before any write. Applying
the migration follows these steps:
- Recheck the preview under the lock and stage all new files.
- Split each legacy Agent table into a file while preserving its table text and attaching pre-table comments to that Agent. Registry header comments can be discarded. Verify that the parsed definition is equal.
- Add subagent frontmatter without changing its instruction body. Move
standalone comments from each legacy table into that resource's frontmatter;
preserve comments without a resource in
layout.toml. Verify the parsed metadata, body, and platform options are equal to the legacy pair. - Install staged files and remove legacy files in one journaled transaction.
Write
layout.tomllast, then verify the new layout before committing the transaction. Retain a recovery copy until the transaction is committed. Interrupted runs recover or stop on external edits.
The command is idempotent. It never resolves conflicting duplicate definitions
by preference. If both layouts contain the same resource before migration,
the plan reports a collision and requires the user to resolve it. The
migration-only parser can read old files; normal resource loaders cannot.
Before building a new plan, the command recovers its own pending transaction
under the lock; an external edit that prevents safe recovery remains blocked.
Comments from subagents.toml remain in a resource's frontmatter or, when the
registry has no resources, in layout.toml; the transaction recovery copy is
temporary and is removed after a successful migration. A partially migrated
workspace cannot run normal commands because the completion marker has not
been written.
Implementation and verification boundary¶
The migration updates initialization, Agent and subagent loaders,
add/rm, doctor, status, runtime synchronization, registry maintenance,
and logical resource snapshots together. Legacy fixtures exercise the
migration gate and migration-only parser. Tests cover fresh workspaces,
collisions, metadata round trips, dry-run zero writes, interrupted migration,
recovery, and idempotency. They also verify that normal commands fail with an
actionable prompt before migration and work afterward.
Because migration adds a CLI command and changes initialized files, Ubuntu,
macOS, and Windows CI each execute the real command and assert the new
files exist, the legacy files are absent, and the completion marker is present.
aikito import workspace <source> [--dry-run] exposes workspace import. It
imports inbox notes, global skill selections, subagents, MCP definitions,
projects, memory, skills, Agent definitions, workspace configuration fields,
and global instructions. Missing projects are
created in the target workspace without requiring a local code checkout. Skill
selections and project paths/skills merge by member. Project configuration
adopts source fields when the target field is absent or still at its init
default; an unmodified project instructions template is replaced by the source.
A customized target is kept when the source is still at a template. Other
differing resources conflict and remain unchanged while the reference-safe
subset applies. Missing references prune the offending logical changes and
their dependents to a fixed point; unresolved preexisting reference errors are
global findings. --keep-target maps a conflict to NOOP and --take-source
maps it to UPDATE, both by resource ID and through the same plan and writer.
Reference checks run again after choices. Shared TOML rendering includes only
selected members, including when creating a previously absent project file. Preview validates references against the
whole result, reports managed-area findings together, and treats possible
plaintext credentials as warnings. Changing the effective inbox path is blocked
when the target inbox contains notes; the preview reports their count and
location because relocating the path would leave those notes unmanaged. The source workspace is never modified.
Agent definitions, workspace configuration, global instructions, project
instructions, and the project sync_mode use their bundled templates as the
comparison baseline, including every earlier shipped version of a template. A target still at the template
adopts a changed source; a customized target is retained when the source still
matches the template. Independently changed values conflict. TOML field merges
retain unrelated target fields and comments.
Import execution uses workspace.resource_write: each logical resource carries
its source and expected target fingerprint. Execution reuses the snapshots from
the locked plan recheck, captures physical file versions before rendering, and
scans the target again for final verification. A single storage contract in
workspace.resources classifies both snapshots and writes. Shared TOML changes
are composed into one replacement per physical file, then workspace.transactions
stages and installs the complete batch. A fresh snapshot must match the
expected logical resources, preserve target-only resources, and have valid
references before the transaction commits. Verification failures roll back the
whole batch, just like write failures.
Version 2 transaction journals record each final destination and the path policy used during staging, including the current and planned inbox prefixes. Recovery validates these paths and retains caller-owned resource/state permissions; it does not infer the inbox location from staged TOML. Version 1 journals still use the caller's path policy.
Internal Resource Reconciliation¶
workspace.reconcile connects one workspace replica to a FilesystemRemote.
This remains an internal API and is separate from runtime aikito sync.
Supported kinds are memory, project-memory, skill, inbox,
global-instructions, project-instructions, agent, mcp, subagent,
skill-selection, project, project-field, project-path, project-skill,
and config. Bundled skill contents, .local, Git metadata, generated runtime
entries, and scanner exclusions stay outside reconciliation. Selections of
bundled skills are shared; their providers come from the installed package.
The host-local configuration exclusion is config:inbox.path. Each replica
keeps its own inbox prefix and receives notes by logical name beneath that
prefix; the center uses inbox/. An inbox outside the workspace blocks inbox
reconciliation, including absence-based deletion. Shared preferences such as
memory.stale_days and update.check participate. Project path candidates,
including offline candidates, are set members; runtime sync still decides
which path is available locally.
The resource center is an initially empty directory, not another workspace.
It stores canonical resource content and a manifest at
.local/state/aikito/workspace-reconcile/remote.json. The manifest contains
sync_id, a monotonic revision, and fingerprints and references keyed by
logical resource ID. Version 2 also stores typed TOML field payloads, retaining
actual key components and values through a TOML encoding. Shared config and
project files are not copied to the center: only accepted fields and collection
members enter its manifest. Version 1 centers remain readable and upgrade on
an accepted write; their project notes require a project provider before that
upgrade can commit. Content is supplied to the writer by ID, so the source
content location does not need to match a workspace layout. Physical storage paths are validated
implementation details rather than persisted comparison identities.
Each replica keeps .local/state/aikito/workspace-reconcile/replica.json with
its sync_id, replica_id, confirmed revision, and a per-resource base.
Replica and center root locations are not comparison identities; project path
candidates remain logical resource data. Completed replicas and centers may be
moved without changing identity. Moving an unfinished transaction is
not supported because recovery journals bind their original destinations.
The old internal baseline.json is rejected explicitly; it is not migrated.
First pairing compares against bundled template fingerprints, unions missing
resources, and never propagates deletions. Later rounds use the replica's base
with workspace.merge.compare. Plans use CREATE, UPDATE, DELETE, NOOP,
CONFLICT, and BLOCKED, with local or remote as the write target. Conflict
choices are accepted only for resources currently reported as CONFLICT,
including conflicts found by reference or TOML checks; choices for other IDs
raise an error. They select the local or remote version and become ordinary
writes or deletions. Selecting content restores missing referenced providers
from the selected side. Selecting deletion also removes dependent set members
absent on that side; standalone dependent content requires its own choice.
These inferred changes appear in the preview and pass reference and credential
checks. Contradictory explicit choices remain conflicts. Choices apply to one
round and must not be reused after those conflicts have converged.
The safe subset commits while conflicts and credential-blocked uploads retain their old base. Reference checks include preserved local resources: a skill cannot be deleted if a preserved selection would lose its provider. Project memory and instructions reference their project; MCP and subagent definitions reference Agents. New projects and their dependents can arrive together. Deleting a project cannot orphan preserved instructions, notes, fields, path candidates, or selected skills. Broken existing local references block the round; changes that would break otherwise valid references become conflicts.
Scalar/table field overlaps are reported as preview conflicts rather than
producing an invalid merged document. Shared TOML fields and collection
additions/deletions are composed into one physical write per file. Rendering
retains unrelated typed values and standalone comment lines, but may reformat tables and discard inline comments. Import
continues using its existing renderer. Deleting every project resource removes
its agent.toml; empty managed memory directories left behind are ignored.
Unmanaged entries and unsafe paths still produce findings.
Center writers share a cross-process lock. A batch is accepted only if its expected center identity, revision, and resource snapshot still match. All resource replacements, deletions, and the incremented revision use one transaction, verified before confirmation. Possible plaintext credentials are blocked per logical payload before upload, including content supplied outside a workspace. A secret in one shared field does not prevent safe fields in the same file from advancing; blocked values never enter the center or replica base.
Center and replica commits are separate atomic transactions. Center uploads commit first, then the replica commits downloads and its new base together. If the replica fails after a successful upload, the center keeps that accepted batch and the replica retains its previous base. Recovery rolls back each unfinished batch; the next round recognizes converged uploads and retries any remaining downloads. This protocol does not claim an atomic transaction across both stores. Conflicting resources never advance their base, and an unchanged round does not increment the center revision.
The end-to-end acceptance scenario lives in
tests/workspace_reconcile_acceptance.py. It runs as a real Python invocation
in Ubuntu, macOS, and Windows smoke jobs; focused behavior remains in pytest.
The same two replicas exercise every admitted resource kind, matching
and conflicting shared-field edits, safe progress during conflicts, stale local
and center plans with no-write assertions, shared-file recovery, repeated
execution, and relocation of completed roots. Final snapshots match the center
by logical resource fingerprint while each replica retains its own inbox path.
Source package¶
The aikito.workspace package exports the public Workspace facade and its
result models. api.py implements that facade; paths.py owns workspace
resolution and the persisted workspace pointer. Internal callers import the
owning submodule directly rather than importing implementation helpers from
the package facade.
sync.py coordinates agent runtime synchronization, while inspection.py
provides a shared, lazy inspection context for one command. importing.py and
reconcile.py implement distinct workspace transfer policies over shared
resource classification, comparison, rendering, and filesystem transactions.
transactions.py owns staged writes, journaling, rollback, and crash recovery.
This source organization does not change the canonical workspace data layout.