You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The web dashboard exposes full CRUD affordances for the company configuration — Edit Organization → General / Agents / Departments tabs, the department edit drawer, the agent edit drawer, create dialogs, reorder via drag-drop, and Org Chart drag-drop agent reassignment. All of these live in the Edit Organization and Org Chart pages and feel like "normal" editing from the operator's perspective.
The catch: none of the write paths actually work. The backend never implemented the mutation endpoints. The frontend web/src/api/endpoints/company.ts has a comment (line 41) that has been there since this area of the dashboard was built:
// ── Mutation stubs (backend endpoints not yet implemented) ───
And indeed, querying the backend's /api/v1 for CompanyController, DepartmentController, and AgentController confirms it — every controller is read-only except for the ceremony-policy sub-resource on DepartmentController. Concretely, hitting any of the following returns 405 Method Not Allowed:
Frontend call
Backend status
PATCH /company (updateCompany)
missing
POST /departments (createDepartment)
missing
PATCH /departments/{name} (updateDepartment)
missing
DELETE /departments/{name} (deleteDepartment)
missing
POST /company/reorder-departments (reorderDepartments)
missing
POST /agents (createAgentOrg)
missing
PATCH /agents/{name} (updateAgentOrg)
missing
DELETE /agents/{name} (deleteAgent)
missing
POST /departments/{name}/reorder-agents (reorderAgents)
missing
This was discovered during local operator testing of PR #1074 when editing a department and clicking Save produced a 405. The UI offered no indication that editing is not yet available, so the operator assumed their install was broken.
The existing writable surfaces in this area are onlyPUT /departments/{name}/ceremony-policy and DELETE /departments/{name}/ceremony-policy (the ceremony policy is a sub-resource with its own CRUD). Everything above those is read-only.
Proposed fix
Backend (src/synthorg/api/controllers/)
Add handlers for all 9 missing mutations. The persistence story is already in place: ConfigResolver.get_departments() / get_agents() read from SettingsService under the company namespace with keys departments and agents, and SettingsService.set() can write new values back. The write handlers should:
Read the current list via resolver.get_departments() / resolver.get_agents().
Apply the mutation (add / update / remove / reorder) with full Pydantic validation (not ad-hoc dict merges).
Write the updated list back via settings_service.set(namespace="company", key="departments"|"agents", value=[...]).
Broadcast a WebSocket event on the agents / departments channel so other connected dashboards update in-place (consistent with the existing read-side WS plumbing in useOrgChartData).
Return the updated resource envelope in the project's standard ApiResponse[T] shape.
Endpoints to add:
PATCH /api/v1/company -- update top-level company metadata (name, communication pattern, monthly budget, currency, etc.)
POST /api/v1/departments -- create a new department (must not duplicate an existing name)
DELETE /api/v1/departments/{name} -- delete a department; must reject (or reassign) if agents are still attached, and must refuse to delete the executive department while the CEO lives there
POST /api/v1/company/reorder-departments -- accept an ordered_names: list[str] and rewrite the departments tuple in that order
POST /api/v1/agents -- create a new agent in a specified department
PATCH /api/v1/agents/{name} -- update an agent (role, level, department -- the Org Chart drag-drop uses this to move an agent between departments)
DELETE /api/v1/agents/{name} -- delete an agent
POST /api/v1/departments/{name}/reorder-agents -- accept an ordered_names: list[str] and rewrite the agent order scoped to that department
Cross-cutting concerns
Guards / auth: all mutations must go through require_write_access (or a narrower require_ceo_or_manager for structural changes), mirroring the ceremony-policy handlers that already exist on DepartmentController.
Validation: reuse the Pydantic models from core.company (Department, AgentConfig) for full schema validation at the boundary. Do not hand-roll field checks in the handler.
Concurrent edits: add ETag / If-Match optimistic concurrency (project already has the infrastructure -- see api/etag.py), so two operators editing the same department don't silently clobber each other.
Referential integrity:
Deleting a department with agents attached -> 409 Conflict with a helpful message ("Reassign or remove the N agents first").
Deleting an agent that is the current CEO -> 409 Conflict unless the caller explicitly opts in to CEO reassignment.
Creating / updating with a department name that does not exist -> 422.
Reorder endpoints must validate that the payload is a complete permutation of the existing names (no additions, no removals) and 422 otherwise.
WebSocket broadcast: publish agents.* / departments.* events so useCompanyStore.updateFromWsEvent can reconcile on other open tabs. The event schema should mirror what the read side already consumes.
Event logging: new events under synthorg.observability.events.company (or reuse an existing namespace) -- DEPARTMENT_CREATED, DEPARTMENT_UPDATED, DEPARTMENT_DELETED, DEPARTMENT_REORDERED, AGENT_CREATED, AGENT_UPDATED, AGENT_DELETED, AGENTS_REORDERED, COMPANY_UPDATED.
Frontend (web/src/api/endpoints/company.ts)
Remove the "Mutation stubs" comment at line 41 once the backend lands.
No signature changes required -- the stubs already return the right shapes; they just need to actually reach a live backend.
Remove any short-term "Coming soon" UI gates (see "Short-term UX" below) once the real endpoints land.
Extend the WS reducer in useCompanyStore and useAgentsStore to handle the new event types emitted by the backend.
Tests
Unit tests per handler covering: happy path, 404 on missing resource, 409 on referential integrity failures, 422 on invalid payload, ETag mismatch, auth failure.
Integration tests that round-trip config through SettingsService (read -> mutate -> read again).
Frontend Vitest tests for the store reducers consuming the new WS events.
While the backend work is scheduled, the dashboard hides or disables every write path and surfaces a single banner pointing at this issue so operators do not hit silent 405s:
Edit Organization page: top-of-page info banner linking to this issue.
DepartmentEditDrawer: Save / Delete buttons disabled with tooltip.
DepartmentCreateDialog, AgentCreateDialog: disabled trigger buttons with tooltip.
Agent performance history writes and artifact writes -- separate controllers, not in scope.
YAML round-tripping. The source of truth after setup is the SettingsService / database; YAML is read once on first boot and then SettingsService owns the state.
Acceptance criteria
All 9 endpoints land on the backend with full Pydantic validation and project-standard ApiResponse shape.
Every mutation emits a domain event under synthorg.observability.events.* and a WebSocket broadcast on the corresponding channel.
Referential integrity: deleting a department with attached agents returns 409, not a dangling write; deleting a CEO agent without opt-in returns 409.
Optimistic concurrency: repeated PATCH with a stale ETag returns 412 Precondition Failed.
Backend unit + integration test coverage for happy path, 404, 409, 422, 412, auth failure on every handler.
Frontend removes the "Mutation stubs" comment in web/src/api/endpoints/company.ts and the short-term "Coming soon" UI gates.
Frontend WS reducers handle the new event types so multi-tab edits stay in sync.
useCompanyStore.optimisticReassignAgent (already present, currently hits a dead endpoint) actually persists the new assignment.
Context
The web dashboard exposes full CRUD affordances for the company configuration — Edit Organization → General / Agents / Departments tabs, the department edit drawer, the agent edit drawer, create dialogs, reorder via drag-drop, and Org Chart drag-drop agent reassignment. All of these live in the Edit Organization and Org Chart pages and feel like "normal" editing from the operator's perspective.
The catch: none of the write paths actually work. The backend never implemented the mutation endpoints. The frontend
web/src/api/endpoints/company.tshas a comment (line 41) that has been there since this area of the dashboard was built:// ── Mutation stubs (backend endpoints not yet implemented) ───And indeed, querying the backend's
/api/v1forCompanyController,DepartmentController, andAgentControllerconfirms it — every controller is read-only except for the ceremony-policy sub-resource onDepartmentController. Concretely, hitting any of the following returns405 Method Not Allowed:PATCH /company(updateCompany)POST /departments(createDepartment)PATCH /departments/{name}(updateDepartment)DELETE /departments/{name}(deleteDepartment)POST /company/reorder-departments(reorderDepartments)POST /agents(createAgentOrg)PATCH /agents/{name}(updateAgentOrg)DELETE /agents/{name}(deleteAgent)POST /departments/{name}/reorder-agents(reorderAgents)This was discovered during local operator testing of PR #1074 when editing a department and clicking Save produced a 405. The UI offered no indication that editing is not yet available, so the operator assumed their install was broken.
The existing writable surfaces in this area are only
PUT /departments/{name}/ceremony-policyandDELETE /departments/{name}/ceremony-policy(the ceremony policy is a sub-resource with its own CRUD). Everything above those is read-only.Proposed fix
Backend (
src/synthorg/api/controllers/)Add handlers for all 9 missing mutations. The persistence story is already in place:
ConfigResolver.get_departments()/get_agents()read fromSettingsServiceunder thecompanynamespace with keysdepartmentsandagents, andSettingsService.set()can write new values back. The write handlers should:resolver.get_departments()/resolver.get_agents().settings_service.set(namespace="company", key="departments"|"agents", value=[...]).agents/departmentschannel so other connected dashboards update in-place (consistent with the existing read-side WS plumbing inuseOrgChartData).ApiResponse[T]shape.Endpoints to add:
PATCH /api/v1/company-- update top-level company metadata (name, communication pattern, monthly budget, currency, etc.)POST /api/v1/departments-- create a new department (must not duplicate an existing name)PATCH /api/v1/departments/{name}-- update an existing department (display name, budget percent, description, teams list -- the teams list is also what feat: in-dashboard team editing -- create, rename, reassign, delete teams within a department #1079 wants to unblock)DELETE /api/v1/departments/{name}-- delete a department; must reject (or reassign) if agents are still attached, and must refuse to delete theexecutivedepartment while the CEO lives therePOST /api/v1/company/reorder-departments-- accept anordered_names: list[str]and rewrite the departments tuple in that orderPOST /api/v1/agents-- create a new agent in a specified departmentPATCH /api/v1/agents/{name}-- update an agent (role, level, department -- the Org Chart drag-drop uses this to move an agent between departments)DELETE /api/v1/agents/{name}-- delete an agentPOST /api/v1/departments/{name}/reorder-agents-- accept anordered_names: list[str]and rewrite the agent order scoped to that departmentCross-cutting concerns
require_write_access(or a narrowerrequire_ceo_or_managerfor structural changes), mirroring the ceremony-policy handlers that already exist onDepartmentController.core.company(Department,AgentConfig) for full schema validation at the boundary. Do not hand-roll field checks in the handler.api/etag.py), so two operators editing the same department don't silently clobber each other.departmentname that does not exist -> 422.budget_percentmay deviate from 100. Emit a warning log (matching feat: rebalance department budgets on pack application + validate total !=100% #1080's approach) but do not block the write -- feat: rebalance department budgets on pack application + validate total !=100% #1080 tracks the full rebalance flow.agents.*/departments.*events souseCompanyStore.updateFromWsEventcan reconcile on other open tabs. The event schema should mirror what the read side already consumes.synthorg.observability.events.company(or reuse an existing namespace) --DEPARTMENT_CREATED,DEPARTMENT_UPDATED,DEPARTMENT_DELETED,DEPARTMENT_REORDERED,AGENT_CREATED,AGENT_UPDATED,AGENT_DELETED,AGENTS_REORDERED,COMPANY_UPDATED.Frontend (
web/src/api/endpoints/company.ts)useCompanyStoreanduseAgentsStoreto handle the new event types emitted by the backend.Tests
SettingsService(read -> mutate -> read again).Short-term UX (already landed in PR #1074)
While the backend work is scheduled, the dashboard hides or disables every write path and surfaces a single banner pointing at this issue so operators do not hit silent 405s:
DepartmentEditDrawer: Save / Delete buttons disabled with tooltip.DepartmentCreateDialog,AgentCreateDialog: disabled trigger buttons with tooltip.GeneralTab: Save button disabled with tooltip.DepartmentsTab+AgentsTab: drag-drop reorder disabled.OrgChartPage: drag-drop agent reassignment disabled (nodesDraggable={false}in hierarchy view).Once this issue is implemented, the UX gates should be removed in the same PR so the experience snaps back to normal.
Out of scope
PATCH /departments/{name}endpoint landed here.PATCH /departments/{name}(theteamsfield flows through the same handler).SettingsService/ database; YAML is read once on first boot and thenSettingsServiceowns the state.Acceptance criteria
ApiResponseshape.synthorg.observability.events.*and a WebSocket broadcast on the corresponding channel.PATCHwith a stale ETag returns 412 Precondition Failed.web/src/api/endpoints/company.tsand the short-term "Coming soon" UI gates.useCompanyStore.optimisticReassignAgent(already present, currently hits a dead endpoint) actually persists the new assignment.Related
PATCH /departments/{name}.PATCH /departments/{name}and the budget-total validator.src/synthorg/core/company.py(Department,AgentConfig),src/synthorg/settings/resolver.py(read path),src/synthorg/settings/service.py(write path).web/src/api/endpoints/company.ts,web/src/stores/company.ts,web/src/stores/agents.ts,web/src/pages/OrgEditPage.tsx,web/src/pages/OrgChartPage.tsx.