From ffc5becec905a240554da4948b20c7603dd7bd5e Mon Sep 17 00:00:00 2001 From: Minura Punchihewa Date: Fri, 19 Jun 2026 14:54:48 -0700 Subject: [PATCH 1/6] removed project dir renaming --- cowork/services/projects.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/cowork/services/projects.py b/cowork/services/projects.py index 3834ea6..6f99129 100644 --- a/cowork/services/projects.py +++ b/cowork/services/projects.py @@ -112,14 +112,8 @@ def update_project( if project.name == GENERAL_PROJECT: raise ValueError("Cannot rename the General project") sanitized = self._sanitize_name(name) - final_name = self._unique_name(sanitized, exclude=project.name) - if final_name != project.name: - old_path = Path(project.path) - new_path = self._project_path(final_name) - if old_path.exists(): - old_path.rename(new_path) - project.name = final_name - project.path = str(new_path) + final_name = self._unique_name(sanitized) + project.name = final_name if is_active is not None: if is_active: From 734f8cf99ec09f6f1fd00f3abf8d13e756026614 Mon Sep 17 00:00:00 2001 From: Minura Punchihewa Date: Fri, 19 Jun 2026 15:01:14 -0700 Subject: [PATCH 2/6] enabled the path to be defined in creating project --- cowork/api/v1/endpoints/projects.py | 2 +- cowork/schemas/projects.py | 9 ++++++++- cowork/services/projects.py | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cowork/api/v1/endpoints/projects.py b/cowork/api/v1/endpoints/projects.py index 578d940..886937c 100644 --- a/cowork/api/v1/endpoints/projects.py +++ b/cowork/api/v1/endpoints/projects.py @@ -20,7 +20,7 @@ def list_projects(session: SessionDep): @router.post("/", status_code=status.HTTP_201_CREATED) def create_project(body: ProjectCreateRequest, session: SessionDep): - return ProjectService(session).create_project(body.name) + return ProjectService(session).create_project(body.name, body.path) @router.patch("/{project_id}") diff --git a/cowork/schemas/projects.py b/cowork/schemas/projects.py index a1205dd..0bfce84 100644 --- a/cowork/schemas/projects.py +++ b/cowork/schemas/projects.py @@ -1,10 +1,17 @@ -from pydantic import BaseModel +from pathlib import Path +from pydantic import field_validator from cowork.schemas.base import CamelRequest class ProjectCreateRequest(CamelRequest): name: str + path: Path | None = None + + @field_validator("path") + @classmethod + def validate_path(cls, path: str | None) -> Path | None: + return Path(path) if path else None class ProjectUpdateRequest(CamelRequest): diff --git a/cowork/services/projects.py b/cowork/services/projects.py index 6f99129..3a8c6d5 100644 --- a/cowork/services/projects.py +++ b/cowork/services/projects.py @@ -86,10 +86,10 @@ def get_project_by_name(self, name: str) -> Project: def get_project_by_name_or_none(self, name: str) -> Project | None: return self.session.exec(select(Project).where(Project.name == name)).first() - def create_project(self, name: str) -> Project: + def create_project(self, name: str, path: Path | None = None) -> Project: sanitized = self._sanitize_name(name) final_name = self._unique_name(sanitized) - path = self._project_path(final_name) + path = path or self._project_path(final_name) path.mkdir(parents=True) # self._scaffold(path) project = Project(name=final_name, path=str(path), is_active=False) From 0b00cc949d8f86d6a709139b5af4669e400da56f Mon Sep 17 00:00:00 2001 From: Minura Punchihewa Date: Fri, 19 Jun 2026 15:17:15 -0700 Subject: [PATCH 3/6] fixed dir creation --- cowork/services/projects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cowork/services/projects.py b/cowork/services/projects.py index 3a8c6d5..380e9e3 100644 --- a/cowork/services/projects.py +++ b/cowork/services/projects.py @@ -90,7 +90,7 @@ def create_project(self, name: str, path: Path | None = None) -> Project: sanitized = self._sanitize_name(name) final_name = self._unique_name(sanitized) path = path or self._project_path(final_name) - path.mkdir(parents=True) + path.mkdir(parents=True, exist_ok=True) # self._scaffold(path) project = Project(name=final_name, path=str(path), is_active=False) self.session.add(project) From 493b2de21676a18113f57ef307d08c20b837d666 Mon Sep 17 00:00:00 2001 From: Minura Punchihewa Date: Fri, 19 Jun 2026 15:17:38 -0700 Subject: [PATCH 4/6] removed unused scaffold --- cowork/services/projects.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/cowork/services/projects.py b/cowork/services/projects.py index 380e9e3..b9a7774 100644 --- a/cowork/services/projects.py +++ b/cowork/services/projects.py @@ -35,12 +35,6 @@ def _root_dir(self) -> Path: def _project_path(self, name: str) -> Path: return self._root_dir() / name - # TODO: Move this. This should only be done when using Anton. - def _scaffold(self, target: Path) -> None: - anton_dir = target / ".anton" - anton_dir.mkdir(parents=True, exist_ok=True) - (anton_dir / "anton.md").touch() - def _unique_name(self, base: str, *, exclude: str | None = None) -> str: existing = { p.name for p in self.session.exec(select(Project)).all() @@ -91,7 +85,7 @@ def create_project(self, name: str, path: Path | None = None) -> Project: final_name = self._unique_name(sanitized) path = path or self._project_path(final_name) path.mkdir(parents=True, exist_ok=True) - # self._scaffold(path) + project = Project(name=final_name, path=str(path), is_active=False) self.session.add(project) self.session.commit() From 2880f0b4041262fef907844f2947ad426d2a80b8 Mon Sep 17 00:00:00 2001 From: Minura Punchihewa Date: Fri, 19 Jun 2026 15:34:08 -0700 Subject: [PATCH 5/6] saved instructions to DB --- cowork/api/v1/endpoints/projects.py | 7 +++-- .../55a019954465_add_project_instructions.py | 28 +++++++++++++++++++ cowork/models/project.py | 2 +- cowork/schemas/projects.py | 2 ++ cowork/services/projects.py | 8 ++++-- 5 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 cowork/db/alembic/versions/55a019954465_add_project_instructions.py diff --git a/cowork/api/v1/endpoints/projects.py b/cowork/api/v1/endpoints/projects.py index 886937c..f8a7ebe 100644 --- a/cowork/api/v1/endpoints/projects.py +++ b/cowork/api/v1/endpoints/projects.py @@ -20,14 +20,17 @@ def list_projects(session: SessionDep): @router.post("/", status_code=status.HTTP_201_CREATED) def create_project(body: ProjectCreateRequest, session: SessionDep): - return ProjectService(session).create_project(body.name, body.path) + try: + return ProjectService(session).create_project(body.name, body.path, body.instructions) + except ValueError as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) @router.patch("/{project_id}") def update_project(project_id: UUID, body: ProjectUpdateRequest, session: SessionDep): try: return ProjectService(session).update_project( - project_id, name=body.name, is_active=body.is_active + project_id, name=body.name, is_active=body.is_active, instructions=body.instructions ) except ValueError as e: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) diff --git a/cowork/db/alembic/versions/55a019954465_add_project_instructions.py b/cowork/db/alembic/versions/55a019954465_add_project_instructions.py new file mode 100644 index 0000000..d1bc737 --- /dev/null +++ b/cowork/db/alembic/versions/55a019954465_add_project_instructions.py @@ -0,0 +1,28 @@ +"""add project instructions + +Revision ID: 55a019954465 +Revises: c4e7a1b9d2f0 +Create Date: 2026-06-19 15:28:23.246712 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '55a019954465' +down_revision: Union[str, Sequence[str], None] = 'c4e7a1b9d2f0' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + op.add_column("projects", sa.Column("instructions", sa.Text(), nullable=True)) + + +def downgrade() -> None: + """Downgrade schema.""" + op.drop_column("projects", "instructions") diff --git a/cowork/models/project.py b/cowork/models/project.py index 0be2db8..1c43bce 100644 --- a/cowork/models/project.py +++ b/cowork/models/project.py @@ -12,4 +12,4 @@ class Project(BaseSQLModel, table=True): max_length=1024, ) is_active: bool = Field(default=True, description="Whether the project is active") - + instructions: str | None = Field(default=None, description="Instructions for the project") diff --git a/cowork/schemas/projects.py b/cowork/schemas/projects.py index 0bfce84..5c8ac64 100644 --- a/cowork/schemas/projects.py +++ b/cowork/schemas/projects.py @@ -7,6 +7,7 @@ class ProjectCreateRequest(CamelRequest): name: str path: Path | None = None + instructions: str | None = None @field_validator("path") @classmethod @@ -17,3 +18,4 @@ def validate_path(cls, path: str | None) -> Path | None: class ProjectUpdateRequest(CamelRequest): name: str | None = None is_active: bool | None = None + instructions: str | None = None \ No newline at end of file diff --git a/cowork/services/projects.py b/cowork/services/projects.py index b9a7774..6226773 100644 --- a/cowork/services/projects.py +++ b/cowork/services/projects.py @@ -80,13 +80,13 @@ def get_project_by_name(self, name: str) -> Project: def get_project_by_name_or_none(self, name: str) -> Project | None: return self.session.exec(select(Project).where(Project.name == name)).first() - def create_project(self, name: str, path: Path | None = None) -> Project: + def create_project(self, name: str, path: Path | None = None, instructions: str | None = None) -> Project: sanitized = self._sanitize_name(name) final_name = self._unique_name(sanitized) path = path or self._project_path(final_name) path.mkdir(parents=True, exist_ok=True) - project = Project(name=final_name, path=str(path), is_active=False) + project = Project(name=final_name, path=str(path), is_active=False, instructions=instructions) self.session.add(project) self.session.commit() self.session.refresh(project) @@ -97,6 +97,7 @@ def update_project( project_id: UUID, name: str | None = None, is_active: bool | None = None, + instructions: str | None = None, ) -> Project: project = self.session.get(Project, project_id) if project is None: @@ -117,6 +118,9 @@ def update_project( self.session.add(other) project.is_active = is_active + if instructions is not None: + project.instructions = instructions or None + self.session.add(project) self.session.commit() self.session.refresh(project) From 452bde6558338bea9a9fb222ffed641e5b7e98e1 Mon Sep 17 00:00:00 2001 From: Minura Punchihewa Date: Fri, 19 Jun 2026 17:09:21 -0700 Subject: [PATCH 6/6] passed project instructions to harnesses --- cowork/harnesses/anton_harness/harness.py | 2 ++ cowork/harnesses/hermes_harness/harness.py | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cowork/harnesses/anton_harness/harness.py b/cowork/harnesses/anton_harness/harness.py index 98797e3..63fbda2 100644 --- a/cowork/harnesses/anton_harness/harness.py +++ b/cowork/harnesses/anton_harness/harness.py @@ -309,6 +309,8 @@ def _settings_path(value: object, fallback: Path) -> Path: f"Your scratchpad's working directory is {str(base)} — bare relative paths like `open('data.csv')` resolve from the project root." + attachment_context ) + if conversation.project.instructions: + project_context += f"\n\nProject instructions: {conversation.project.instructions}" output_context = ( # Artifacts now live in their own visible folder at the # project root (`/artifacts//...`), one folder diff --git a/cowork/harnesses/hermes_harness/harness.py b/cowork/harnesses/hermes_harness/harness.py index 9c03d6a..91fbac0 100644 --- a/cowork/harnesses/hermes_harness/harness.py +++ b/cowork/harnesses/hermes_harness/harness.py @@ -185,8 +185,7 @@ def run_sync() -> dict: str(conversation.id), prompt, history, - project_name=conversation.project.name, - project_path=project_path, + project=conversation.project, conversation_topic=conversation_topic, stream_callback=stream_callback, tool_start_callback=tool_start_callback, @@ -227,8 +226,7 @@ def _run( prompt: str, history: list[dict], *, - project_name: str, - project_path: str, + project: Project, conversation_topic: str | None = None, stream_callback=None, tool_start_callback=None, @@ -257,6 +255,9 @@ def _run( register_connector_tools() register_artifact_tools() + project_path = project.path + project_name = project.name + # Same folder-per-artifact convention as the Anton harness, so # Hermes outputs surface in the (harness-agnostic) Artifacts UI. artifacts_root = Path(project_path) / ".anton" / "artifacts" @@ -308,6 +309,8 @@ def _run( "The only other files that you are allowed to access are any items that are attached to the conversation." "Access to any files not attached to the conversation or located outside the project is strictly forbidden." ) + if project.instructions: + project_context += f"\n\nProject instructions: {project.instructions}" datasource_context = _build_datasource_context(vault, disabled_keys) memory_context = HermesMemoryAdapter().build_prompt_context(Path(project_path)) system_context = "\n\n".join(