Document ordinary external workspaces

This commit is contained in:
Ouroboros 2026-09-13 16:26:06 +03:00 • committed by Ouroboros
parent c872ae61f9
commit 0d0970a259
4 changed files with 22 additions and 3 deletions

View file

@ -291,7 +291,7 @@ ouroboros schedule add --name nightly-review --cron "0 2 * * *" "Run a maintenan
ouroboros schedule list
```
External workspaces must be separate Git worktree roots and may not overlap Ouroboros's own repository or data directory. Patch, streaming, detached-task, and schedule semantics are documented in the CLI help and the canonical [architecture](docs/ARCHITECTURE.md).
External workspaces may be ordinary folders or separate Git worktree roots, and may not overlap Ouroboros's own repository or data directory. Git-specific operations use a Git worktree; ordinary file and process work runs directly in a validated folder. Patch, streaming, detached-task, and schedule semantics are documented in the CLI help and the canonical [architecture](docs/ARCHITECTURE.md).
#### For Agents

View file

@ -505,7 +505,7 @@ Release builds also carry Node and ripgrep. Skill-side Node resolves bundled-fir
The embedded interpreter must never write into the signed application (codesign seal): entry processes suppress bytecode before project imports, and `embedded_python_env()` redirects bytecode to `data/state/pycache` and user installs to `data/state/python-userbase` (`pip_install_target_args()` adds `--user` for direct `python-standalone` invocations and aliases). The pip helper first recognizes an invoked virtual environment from `pyvenv.cfg` beside its standard `bin`/`Scripts` directory, before resolving the executable symlink; confirmed venv installs stay inside that environment. Disclosed residual: the userbase outranks bundle site-packages and nothing prunes or versions it — recovery is manual (remove the directory, relaunch).
Workspace binding changes the contextual repo, never the system repo for BIBLE/prompts/review governance. `/api/tasks` and project-room promotion share `workspace_admission.validate_workspace_root()` (exists, exact worktree root, disjoint from the system repo and data drive, resolved/bidirectional/case-folded); an empty Project binding is idempotently provisioned as a standalone git repo unless `workspace="none"`. Binding changes the default file/process/VCS target plus memory/lease/preflight/finalization; it does not remove top-level tools or downgrade the Architecture context in Max mode (`root=system_repo` stays; `root=skill_payload` takes bucket+skill_name). The workspace executor is a process-routing boundary: `executor_ref` is host-owned, mappings must cover the workspace without overlapping system repo/data, `network=none` only when the backend implements it, and executor processes enter durable custody records.
Workspace binding changes the contextual repo, never the system repo for BIBLE/prompts/review governance. `/api/tasks` and project-room promotion share `workspace_admission.validate_workspace_root()` (exists, ordinary directory or exact Git worktree root, disjoint from the system repo and data drive, resolved/bidirectional/case-folded); an empty Project binding is idempotently provisioned as a standalone git repo unless `workspace="none"`. Ordinary folders support direct file/process work; Git-specific operations require a Git worktree. Binding changes the default file/process/VCS target plus memory/lease/preflight/finalization; it does not remove top-level tools or downgrade the Architecture context in Max mode (`root=system_repo` stays; `root=skill_payload` takes bucket+skill_name). The workspace executor is a process-routing boundary: `executor_ref` is host-owned, mappings must cover the workspace without overlapping system repo/data, `network=none` only when the backend implements it, and executor processes enter durable custody records.
Workspace preflight snapshots git state (bounded porcelain rows), manifests/scripts, and tool availability into the full `workspace_preflight.json` artifact with a bounded summary in metadata; `tools_on_path`/`tools_missing_from_path` are named that way because `shutil.which` measures PATH presence, not executability, and the structured keys stay frozen because they ride durable, replaying task metadata; a collection failure is a disclosed error summary, never a fictitious full artifact.

View file

@ -158,7 +158,7 @@ def get_tools() -> List[ToolEntry]:
"project_name": {"type": "string", "description": "Set ONLY to create a brand-new NAMED project now and run this task inside it (e.g. 'airi research'). The display name; a filesystem id is derived from it.", "default": ""},
"expected_output": {"type": "string", "description": "What done looks like.", "default": ""},
"project_id": {"type": "string", "description": "Optional EXISTING project scope (filesystem-clean id).", "default": ""},
"workspace_root": {"type": "string", "description": "Optional absolute working-folder path (validated at admission: must be a git worktree root outside the Ouroboros repo/data). When omitted for a project-scoped task, the project's registered working_dir is used by default.", "default": ""},
"workspace_root": {"type": "string", "description": "Optional absolute working-folder path (validated at admission as an ordinary folder or Git worktree root outside the Ouroboros repo/data). Git-specific operations require a Git worktree; ordinary file and process work is supported directly in a validated folder. When omitted for a project-scoped task, the project's registered working_dir is used by default.", "default": ""},
"workspace": {"type": "string", "description": "Pass 'none' to opt OUT of the project room's default working folder (a folder-less task in a folder-ful project). Leave empty otherwise.", "default": ""},
"source": {"type": "string", "description": "Attach or clone the project's working folder in ONE move: a git URL (https://... or git@host:path — cloned server-side into the projects root; private repos fail typed auth_required) or an existing folder path (validated attach). The folder is registered on the project (provenance + trusted_at) and becomes this task's active workspace. Use for 'help me debug this GitHub repo / this folder' asks.", "default": ""},
"predecessor_task_id": {"type": "string", "description": "Required explicit selector: pass an empty string for fresh work, or the completed result id shown by the host routing manifest to continue it."},

View file

@ -0,0 +1,19 @@
"""The public workspace contract must keep ordinary folders visible."""
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def test_external_workspace_docs_distinguish_plain_folders_from_git_operations():
readme = (ROOT / "README.md").read_text(encoding="utf-8")
architecture = (ROOT / "docs" / "ARCHITECTURE.md").read_text(encoding="utf-8")
control = (ROOT / "ouroboros" / "tools" / "control.py").read_text(encoding="utf-8")
assert "ordinary folders or separate Git worktree roots" in readme
assert "ordinary file and process work runs directly" in readme
assert "Ordinary folders support direct file/process work" in architecture
assert "ordinary folder or Git worktree root" in control
assert "ordinary file and process work is supported directly" in control
assert "External workspaces must be separate Git worktree roots" not in readme
assert "must be a git worktree root outside" not in control