Component Map

C4-style view of the SYRIS modular monolith, module responsibilities, and extraction seams.

A C4-style view of SYRIS as a modular monolith, its runtime topology, and the seams designed for future extraction.

Module tree

syris/
├── pyproject.toml
├── alembic.ini
├── src/
│   └── syris/
│       ├── __init__.py
│       ├── main.py                          # Boot: wire all components, start loops
│       ├── config.py                        # Typed settings (Pydantic BaseSettings)
│       │
│       ├── schemas/                         # Pure Pydantic v2 — no DB, no logic
│       │   ├── __init__.py
│       │   ├── common.py                    # Enums: ChannelEnum, ActorType, RiskLevel,
│       │   │                                #   AutonomyLevel, Sensitivity, etc.
│       │   │                                # Value types: ErrorDetail
│       │   ├── events.py                    # MessageEvent, RoutingDecision, GateSpec
│       │   ├── tasks.py                     # Task, Step, RetryPolicy, StepOutcome
│       │   ├── tools.py                     # ToolCall, ToolResult, ToolCallContext,
│       │   │                                #   RegisteredTool, ToolHealth, TrustPolicy
│       │   ├── audit.py                     # AuditEvent
│       │   ├── approvals.py                 # Approval
│       │   ├── schedules.py                 # Schedule
│       │   ├── rules.py                     # Rule, Condition, Action types
│       │   └── health.py                    # SystemHealth, Alarm
│       │
│       ├── storage/                         # DB layer (PostgreSQL)
│       │   ├── __init__.py
│       │   ├── db.py                        # Engine, session factory, connection config
│       │   ├── models.py                    # SQLAlchemy ORM models (all tables)
│       │   ├── repos/                       # One repo per aggregate root
│       │   │   ├── __init__.py
│       │   │   ├── events.py
│       │   │   ├── audit.py
│       │   │   ├── tasks.py
│       │   │   ├── tools.py                 # tool_calls, tool_results,
│       │   │   │                            #   idempotency_outcomes
│       │   │   ├── approvals.py
│       │   │   ├── schedules.py
│       │   │   ├── watchers.py
│       │   │   ├── rules.py
│       │   │   ├── health.py                # system_health, alarms
│       │   │   └── projections.py           # proj_* tables read/write
│       │   └── migrations/                  # Alembic
│       │       ├── env.py
│       │       └── versions/
│       │
│       ├── pipeline/                        # The three-stage pipeline
│       │   ├── __init__.py
│       │   ├── runner.py                    # Main loop: normalise → route → execute
│       │   ├── normaliser.py                # Raw payload → MessageEvent + audit
│       │   ├── router.py                    # Orchestrates routing layers in order
│       │   └── executor.py                  # Dispatches to fast/task/gated/sandbox
│       │
│       ├── routing/                         # Router internals (peer to pipeline/)
│       │   ├── __init__.py
│       │   ├── filters.py                   # Hard filters: dedup, spam, quiet hours
│       │   ├── fastpath.py                  # Deterministic intent matching
│       │   ├── rules_eval.py                # Condition evaluator for Rule objects
│       │   └── llm_fallback.py              # LLM-based routing (last resort)
│       │
│       ├── tasks/                           # Task engine
│       │   ├── __init__.py
│       │   ├── engine.py                    # Claim → execute → checkpoint loop
│       │   ├── step_runner.py               # Execute one step, handle outcomes
│       │   ├── state.py                     # State machine enforcement
│       │   └── recovery.py                  # Startup reconciliation
│       │
│       ├── tools/                           # Tool runtime
│       │   ├── __init__.py
│       │   ├── registry.py                  # ToolRegistry: register, lookup, health
│       │   ├── executor.py                  # Scope → risk → gate → idempotency → call
│       │   ├── idempotency.py               # Outcome store operations
│       │   ├── base.py                      # BaseTool ABC
│       │   └── builtin/
│       │       └── noop.py                  # NoopTool for testing
│       │
│       ├── safety/                          # Safety layer
│       │   ├── __init__.py
│       │   ├── autonomy.py                  # Read/write current level + history
│       │   ├── risk.py                      # Risk classifier + adjusters
│       │   ├── gates.py                     # Gate matrix + override evaluation
│       │   └── dryrun.py                    # Preview protocol
│       │
│       ├── scheduler/                       # Schedules only
│       │   ├── __init__.py
│       │   └── loop.py                      # Scheduler tick loop
│       │
│       ├── watchers/                        # Watcher subsystem (top-level peer)
│       │   ├── __init__.py
│       │   ├── loop.py                      # Watcher tick loop
│       │   ├── base.py                      # BaseWatcher ABC
│       │   └── heartbeat.py                 # HeartbeatWatcher
│       │
│       ├── rules/                           # Rules engine (top-level peer)
│       │   ├── __init__.py
│       │   ├── engine.py                    # Load rules, evaluate, dispatch actions
│       │   └── cache.py                     # In-memory cache with TTL + invalidation
│       │
│       ├── mcp/                             # MCP integration (top-level peer)
│       │   ├── __init__.py
│       │   ├── connection.py                # MCPConnectionManager
│       │   ├── provider.py                  # MCPProvider: discovery → registry sync
│       │   ├── adapter.py                   # MCPToolAdapter(BaseTool)
│       │   └── trust.py                     # TrustPolicy schema + loader
│       │
│       ├── adapters/                        # Inbound/outbound adapters
│       │   ├── __init__.py
│       │   ├── inbound/
│       │   │   ├── __init__.py
│       │   │   └── base.py                  # InboundAdapter ABC
│       │   └── outbound/
│       │       ├── __init__.py
│       │       └── base.py                  # OutboundAdapter ABC
│       │
│       ├── observability/                   # Audit, projections, health
│       │   ├── __init__.py
│       │   ├── audit.py                     # AuditWriter — sole emit point
│       │   ├── projections.py               # Sync projection updaters
│       │   ├── health.py                    # SystemHealth writer
│       │   └── alarms.py                    # Alarm creation + dedup + lifecycle
│       │
│       ├── secrets/
│       │   ├── __init__.py
│       │   └── store.py                     # SecretsStore protocol + Fernet impl
│       │
│       ├── api/                             # FastAPI
│       │   ├── __init__.py
│       │   ├── app.py                       # FastAPI app factory
│       │   ├── deps.py                      # Dependency injection (session, services)
│       │   └── routes/
│       │       ├── __init__.py
│       │       ├── status.py                # /health, /state
│       │       ├── events.py                # /events
│       │       ├── audit.py                 # /audit, /artifacts/{id}
│       │       ├── tasks.py                 # /tasks CRUD + cancel/pause/resume
│       │       ├── approvals.py             # /approvals + approve/deny
│       │       ├── schedules.py             # /schedules CRUD
│       │       ├── watchers.py              # /watchers
│       │       ├── rules.py                 # /rules
│       │       ├── integrations.py          # /integrations
│       │       ├── controls.py              # /controls/pause, resume, autonomy
│       │       └── alarms.py                # /alarms + ack/resolve
│       │
│       └── workers/                         # Milestone 7 — skeleton only
│           ├── __init__.py
│           └── manager.py
│
└── tests/
    ├── unit/
    ├── integration/
    └── conftest.py

Structural decisions

Flat src/syris/ layout. The previous spec used a nested core/syris_core/src/syris_core/ structure. For a single-developer project with no current extraction need, a flat layout with one pyproject.toml at the root is sufficient. The extraction seam is the module boundary, not package nesting.

routing/ is a top-level peer to pipeline/. pipeline/router.py is a thin orchestrator that delegates to routing/ functions in priority order. The routing internals (filters, fastpath, rules eval, LLM fallback) are a distinct concern from pipeline stage orchestration. Making them peers makes the dependency direction explicit: pipeline depends on routing, not the reverse.

watchers/, rules/, scheduler/ are top-level peers. Each has its own loop, state model, and lifecycle. The previous spec nested watchers and rules under scheduler/, but there is no containment relationship — the scheduler does not manage watchers or rules.

mcp/ is a top-level peer. MCP is a specific, well-defined integration mechanism with four components. The previous spec nested it under a generic integrations/ package alongside inbound/outbound ABCs. MCP has enough weight and structure to justify its own top-level package.

tools/base.py instead of tools/adapter.py. Avoids naming confusion with mcp/adapter.py. Both define abstract base classes; base.py is the conventional name.

config.py added. Typed settings via Pydantic BaseSettings — reads from env vars and .env files, validates at startup, injectable everywhere.

High-level flow

[Inbound Adapters] ──► [normaliser.py] ──► [router.py] ──► [pipeline/executor.py]
        │                    │                                      │
        │                    │ persists MessageEvent + AuditEvent   ├─ fast  ──► [tools/executor.py]
        │                    │                                      ├─ task  ──► [tasks/engine.py]
        │                    │                                      ├─ gated ──► [safety/gates.py]
        │                    │                                      └─ sandbox──► [workers/manager.py]
        │
        └── Scheduler / Watchers / Rules also emit events into normaliser

Process topology

Control Plane (always-on process)
  ├── Ingestion loops (webhooks / pollers)
  ├── Scheduler loop
  ├── Watcher loop
  ├── Task engine loop (claim → execute → checkpoint)
  └── API server (FastAPI + uvicorn)

Worker(s) (optional, gated)
  └── Sandbox jobs; heavy analysis; artifact production

Dependency graph

schemas/          ← depends on nothing
storage/          ← depends on schemas/
observability/    ← depends on schemas/, storage/
secrets/          ← depends on nothing
safety/           ← depends on schemas/, storage/, observability/
tools/            ← depends on schemas/, storage/, observability/, safety/, secrets/
routing/          ← depends on schemas/, rules/
pipeline/         ← depends on schemas/, routing/, tools/, tasks/, safety/, observability/
tasks/            ← depends on schemas/, storage/, tools/, observability/
scheduler/        ← depends on schemas/, storage/, pipeline/, observability/
watchers/         ← depends on schemas/, storage/, pipeline/, observability/
rules/            ← depends on schemas/, storage/
mcp/              ← depends on schemas/, tools/, storage/, observability/
adapters/         ← depends on schemas/, secrets/
api/              ← depends on everything (thin layer)

No circular dependencies. Each module has a clear dependency direction pointing downward toward schemas/ and storage/.

Seams for later extraction

  • Worker boundary — worker process is already a separate runtime; can move to container or remote.
  • Integration adapters — inbound and outbound adapters implement stable ABCs; can move out of the monolith if needed.
  • Projection builderprojections.py updaters run synchronously today; can be replaced by an async projector without changing callers. See observability/projections.