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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ src/synthorg/
memory/ # Pluggable MemoryBackend, retrieval pipeline, org memory, consolidation
persistence/ # Pluggable PersistenceBackend, SQLite, settings + user repositories
observability/ # Structured logging, correlation tracking, redaction, third-party logger taming, events/
providers/ # LLM provider abstraction, presets, model auto-discovery, runtime CRUD (management/), provider families, discovery SSRF allowlist, health tracking, active health probing
providers/ # LLM provider abstraction, presets, model auto-discovery, capabilities, runtime CRUD (management/), provider families, discovery SSRF allowlist, health tracking, active health probing
settings/ # Runtime-editable settings (DB > env > YAML > code), Fernet encryption, ConfigResolver, definitions/, subscribers/
security/ # Rule engine, audit log, output scanner, progressive trust, autonomy levels, timeout policies, LLM fallback evaluator, custom policy rules
templates/ # Pre-built company templates, personality presets, model requirements, tier-to-model matching, locale-aware name generation
Expand Down
2 changes: 2 additions & 0 deletions docs/design/brand-and-ux.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ The following shared components live in `web/src/components/ui/` and form the bu
| `SelectField` | `select-field.tsx` | `label`, `options`, `value`, `onChange`, `error?`, `hint?`, `placeholder?`, `required?`, `disabled?`, `className?` | Labeled select dropdown with error/hint display and placeholder support. |
| `SliderField` | `slider-field.tsx` | `label`, `value`, `onChange`, `min`, `max`, `step?`, `formatValue?`, `disabled?`, `className?` | Labeled range slider with custom value formatter and aria-live value display. |
| `ToggleField` | `toggle-field.tsx` | `label`, `checked`, `onChange`, `description?`, `disabled?` | Labeled toggle switch (role="switch") with optional description text. |
| `TagInput` | `tag-input.tsx` | `label`, `value`, `onChange`, `placeholder?`, `disabled?`, `className?` | Chip-style multi-value input with add/remove, keyboard support (Enter to add, Backspace to remove), paste splitting. |
| `TokenUsageBar` | `token-usage-bar.tsx` | `data`, `segments?`, `max?`, `animated?`, `className?` | Segmented horizontal meter bar for token usage (multi-segment with auto-colors, `role="meter"`, animated transitions). |
| `CodeMirrorEditor` | `code-mirror-editor.tsx` | `value`, `onChange`, `language`, `readOnly?`, `aria-label?`, `className?` | CodeMirror 6 editor with JSON/YAML modes, design-token dark theme, line numbers, bracket matching, and `readOnly` support. |
| `SegmentedControl` | `segmented-control.tsx` | `label`, `options`, `value`, `onChange`, `disabled?`, `size?`, `className?` | Accessible radiogroup with keyboard navigation (arrow keys + wrapping), size variants (`sm`/`md`), generic `<T extends string>` typing. |
| `ThemeToggle` | `theme-toggle.tsx` | `className?` | Radix Popover with 5-axis theme controls (color, density, typography, animation, sidebar). Rendered in StatusBar for global access. |
Expand Down
4 changes: 2 additions & 2 deletions docs/design/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -1184,11 +1184,11 @@ For the full page list, navigation hierarchy, URL routing map, and WebSocket cha

**Secondary navigation** (sidebar, collapsible "Workspace" section):

- **Agents** (`/agents`): Agent profile cards/table. Click opens Agent Detail slide-in panel with tabs: Overview, Performance (collaboration score), Access (autonomy level), Activity (tasks, spending)
- **Agents** (`/agents`): Agent profile cards/table. Click navigates to Agent Detail page (`/agents/{agentName}`) -- single scrollable page with identity header, prose insights, performance metrics, tool badges, career timeline, task history, and activity log
- **Messages** (`/messages`): Channel-filtered agent-to-agent communication feed for investigating delegation chains and coordination
- **Meetings** (`/meetings`): Meeting history, transcripts, outcomes. Trigger meeting action
- **Providers** (`/providers`): LLM provider CRUD, connection test, preset-based creation, model auto-discovery (Ollama `/api/tags`, standard `/models`). Provider routing settings alongside CRUD cards
- **Settings** (`/settings`): Configuration for 7 namespaces (api, memory, budget, security, coordination, observability, backup). Two-column responsive grid, basic/advanced mode, GUI/JSON/YAML toggle. Backup management CRUD nested under backup namespace. System-managed settings hidden from GUI. Environment-sourced settings read-only.
- **Settings** (`/settings`): Configuration for 7 namespaces (api, memory, budget, security, coordination, observability, backup). Namespace tab bar navigation with single-column layout, basic/advanced mode, GUI/Code edit toggle (split-pane diff view for JSON/YAML). Observability sinks sub-page (`/settings/observability/sinks`) for log sink management with card grid and test-before-save. Backup management CRUD nested under backup namespace. System-managed settings hidden from GUI. Environment-sourced settings read-only.
- *DB-backed persistence*: 9 namespaces total (api, company, providers, memory, budget, security, coordination, observability, backup) -- company and providers are managed on their own dedicated pages. Setting types: `STRING`, `INTEGER`, `FLOAT`, `BOOLEAN`, `ENUM`, `JSON`. 4-layer resolution: DB > env > YAML > code defaults. Fernet encryption for `sensitive` values. REST API (`GET`/`PUT`/`DELETE` + schema endpoints for dynamic UI generation), change notifications via message bus.
- *`ConfigResolver`*: Typed scalar accessors assemble full Pydantic config models from individually resolved settings (using `asyncio.TaskGroup` for parallel resolution). Structural data accessors (`get_agents`, `get_departments`, `get_provider_configs`) resolve JSON-typed settings with Pydantic schema validation and graceful fallback to `RootConfig` defaults on invalid data.
- *Hot-reload*: `SettingsChangeDispatcher` polls the `#settings` bus channel and routes change notifications to registered `SettingsSubscriber` implementations. Settings marked `restart_required=True` are filtered (logged as WARNING, not dispatched). Concrete subscribers: `ProviderSettingsSubscriber` (rebuilds `ModelRouter` on `routing_strategy` change via `AppState.swap_model_router`), `MemorySettingsSubscriber` (advisory logging for non-restart memory settings), `BackupSettingsSubscriber` (toggles `BackupScheduler` on `enabled` change, reschedules interval on `schedule_hours` change).
Comment on lines +1191 to 1194
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add the Config Health row to the /settings design summary.

Line 1191 documents the redesigned settings landing page, but it omits the new real-time Config Health strip above the namespace tabs. That row is one of the core dashboard-quality changes in this PR, so leaving it out makes the operations spec incomplete for follow-up work.

Based on learnings: update the relevant docs/design/ page to reflect the new reality.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/operations.md` around lines 1191 - 1194, Update the settings
landing page section for `/settings` to include the new real-time "Config
Health" strip above the namespace tabs: describe its placement, purpose
(aggregated health/status badges for namespaces, quick alerts, last-updated
timestamp), interaction affordances (click-to-filter namespace, hover details,
link to observability sinks), and behavior with respect to
hot-reload/SettingsChangeDispatcher events and restart_required filtering;
reference the existing namespace tab description and
ConfigResolver/SettingsChangeDispatcher/SettingsSubscriber concepts so readers
know how the strip derives its signals and reacts to changes.

Expand Down
13 changes: 9 additions & 4 deletions docs/design/page-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,17 +123,21 @@ No WebSocket subscription -- provider changes are low-frequency admin operations

#### Settings (`/settings`)

Configuration for 7 namespaces: api, memory, budget, security, coordination, observability, backup. Collapsible namespace sections with sub-group headings, basic/advanced mode, GUI/Code edit toggle (JSON/YAML within Code mode). Each namespace is URL-addressable (`/settings/{namespace}`) for deep linking from other pages (e.g. Dashboard budget warning links to `/settings/budget`).
Configuration for 7 namespaces: api, memory, budget, security, coordination, observability, backup. Navigation uses a horizontal **namespace tab bar** across the top of the settings page, with each namespace as a clickable tab. Within each namespace, settings are displayed in a single-column layout with sub-group headings, basic/advanced mode, GUI/Code edit toggle (JSON/YAML within Code mode). Each namespace is URL-addressable (`/settings/{namespace}`) for deep linking from other pages (e.g. Dashboard budget warning links to `/settings/budget`).

The **Code edit mode** uses a **split-pane diff editor view**: the left pane shows the current persisted configuration (read-only), and the right pane is an editable CodeMirror editor for composing changes. This allows operators to see exactly what will change before saving.

Enabling advanced mode for the first time shows a confirmation dialog warning about misconfiguration risk. The warning is deduplicated per session via `sessionStorage`. Advanced mode preference persists in `localStorage`.

Dependency indicators are driven by a frontend-maintained `SETTING_DEPENDENCIES` map in `web/src/utils/constants.ts` (not by backend schema). When a controller setting is disabled, its dependent settings display in a muted state.

The **observability namespace** includes a dedicated **Sinks** sub-page (`/settings/observability/sinks`) for managing log sink configuration. The sinks page displays all active sinks (console and file) as cards showing identifier, log level, format, rotation policy, and routing prefixes. Operators can edit sink overrides and define custom sinks with a test-before-save workflow.

The **backup namespace** will include backup management CRUD (trigger, list, restore, delete) in a future iteration, consolidating the BackupController under the Settings page. The current implementation covers backup configuration settings only (schedule, retention, path).

System-managed settings (e.g. `api/setup_complete`) are hidden from the GUI. Environment-sourced settings display as read-only.

**API endpoints**: `GET /settings/_schema`, `GET /settings/_schema/{ns}`, `GET /settings`, `GET /settings/{ns}`, `GET /settings/{ns}/{key}`, `PUT /settings/{ns}/{key}`, `DELETE /settings/{ns}/{key}`, `POST /admin/backups`, `GET /admin/backups`, `GET /admin/backups/{id}`, `DELETE /admin/backups/{id}`, `POST /admin/backups/restore`
**API endpoints**: `GET /settings/_schema`, `GET /settings/_schema/{ns}`, `GET /settings`, `GET /settings/{ns}`, `GET /settings/{ns}/{key}`, `PUT /settings/{ns}/{key}`, `DELETE /settings/{ns}/{key}`, `GET /settings/observability/sinks`, `POST /settings/observability/sinks/_test`, `POST /admin/backups`, `GET /admin/backups`, `GET /admin/backups/{id}`, `DELETE /admin/backups/{id}`, `POST /admin/backups/restore`
Comment on lines +134 to +140
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Document the write side of the sinks API as well.

This section now promises add/edit/custom sink flows, but the endpoint inventory still only shows GET /settings/observability/sinks and _test. Please list the save/update/enable-disable routes as well—at minimum the collection POST route—so the design page matches the implemented CRUD flow.

Based on learnings, "When approved deviations occur, update the relevant docs/design/ page to reflect the new reality."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/page-structure.md` around lines 134 - 140, The endpoints list is
missing the write-side CRUD for observability sinks; update the API inventory to
include at minimum the collection create route and per-sink update/delete/enable
routes by adding entries such as POST /settings/observability/sinks
(create/save), PUT /settings/observability/sinks/{id} (update), DELETE
/settings/observability/sinks/{id} (delete), and a route for toggling or setting
enabled state like PATCH /settings/observability/sinks/{id}/enable or POST
/settings/observability/sinks/{id}/_enable so the documented API matches the
described add/edit/custom sink flows and the existing POST
/settings/observability/sinks/_test.

**WS channels**: `system` (restart-required notifications)

### Standalone Pages
Expand Down Expand Up @@ -252,8 +256,9 @@ SIDEBAR (220px expanded / 56px icon rail)
| `/meetings/:meetingId` | Meeting detail | Transcript and outcomes |
| `/providers` | Providers | Provider list |
| `/providers/:providerName` | Provider detail | Edit/test provider |
| `/settings` | Settings | Namespace overview |
| `/settings/:namespace` | Settings (filtered) | Single namespace view |
| `/settings` | Settings | Namespace overview (tab bar navigation) |
| `/settings/:namespace` | Settings (filtered) | Single namespace view via tab bar |
| `/settings/observability/sinks` | Settings Sinks | Observability sink management (card grid with edit/test) |
| `*` | 404 Not Found | Catch-all |

### Route Guards
Expand Down
23 changes: 8 additions & 15 deletions src/synthorg/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@
from synthorg.api.controllers import ALL_CONTROLLERS
from synthorg.api.controllers.ws import ws_handler
from synthorg.api.exception_handlers import EXCEPTION_HANDLERS
from synthorg.api.lifecycle import _safe_shutdown, _safe_startup, _try_stop
from synthorg.api.lifecycle import (
_maybe_start_health_prober,
_safe_shutdown,
_safe_startup,
_try_stop,
)
from synthorg.api.middleware import RequestLoggingMiddleware, security_headers_hook
from synthorg.api.state import AppState
from synthorg.api.ws_models import WsEvent, WsEventType
Expand Down Expand Up @@ -80,7 +85,7 @@
from synthorg.persistence.factory import create_backend
from synthorg.persistence.protocol import PersistenceBackend # noqa: TC001
from synthorg.providers.health import ProviderHealthTracker # noqa: TC001
from synthorg.providers.health_prober import ProviderHealthProber
from synthorg.providers.health_prober import ProviderHealthProber # noqa: TC001
from synthorg.providers.registry import ProviderRegistry # noqa: TC001
from synthorg.security.timeout.scheduler import ApprovalTimeoutScheduler # noqa: TC001
from synthorg.settings.dispatcher import SettingsChangeDispatcher
Expand Down Expand Up @@ -372,19 +377,7 @@ async def on_startup() -> None:
name="ws-ticket-cleanup",
)
_ticket_cleanup_task.add_done_callback(_on_cleanup_task_done)
# Start health prober for local providers (after config is resolved)
if app_state.has_provider_health_tracker and app_state.has_config_resolver:
policy_loader = (
app_state.provider_management.get_discovery_policy
if app_state.has_provider_management
else None
)
_health_prober = ProviderHealthProber(
health_tracker=app_state.provider_health_tracker,
config_resolver=app_state.config_resolver,
discovery_policy_loader=policy_loader,
)
await _health_prober.start()
_health_prober = await _maybe_start_health_prober(app_state)

async def on_shutdown() -> None:
nonlocal _ticket_cleanup_task, _auto_wired_dispatcher, _health_prober
Expand Down
Loading
Loading