OpenSRE architecture
How the OpenSRE codebase is structured: the eight first-party packages, what each is responsible for, and which may depend on which. These dependency rules are CI-enforced (make check-imports), so they are real invariants rather than
aspirations.
The layer stack
The packages sit in five tiers. Higher tiers may import lower tiers; a lower tier may never import a higher one. Packages on the same tier are peers — the last column says whether peers may import each other.
The shortcut: dependencies point downward only. A surface can reach all the
way down;
config can reach nothing. The single deliberate exception is
core ⟷ platform, a mutually-dependent pair by design (see below).
The arrows show edges between adjacent tiers to keep the diagram readable.
The actual rule is broader: a tier may import any tier below it, not only
the one directly beneath — so a surface may import config directly, and a
tool may import platform. Refer to the “May import” column above for the
complete set of allowed edges.
The layers in detail
Tier 1 — surfaces and gateway
The entry points a human or an external system talks to. Nothing first-party
may import from here, so a surface can be added or removed without touching the
layers below it.
surfaces/— one folder per UI/client:surfaces/cli(the statelessopensre <command>runner),surfaces/interactive_shell(the stateful REPL), andsurfaces/sharedfor code two or more surfaces use. A surface owns its own I/O, prompts, and presentation, and composes lower layers to do the actual work. The two terminal surfaces are peers and do not import each other (tests/shared/test_surface_border.pypins both directions at zero); what both need lives insurfaces/shared—terminal/(output tracking, tables, prompts, banner, health and feedback rendering),llm_setup/,error_handling/— or a lower layer.surfaces/entrypoint.pyis theopensreconsole script: it hands the CLI aCliHost(how to open the shell, how to run the gateway attached) and hands the shell the CLI’s Click group for grounding, so composition happens in one place above both. Slack is not a surface: its inbound transport lives ingateway/transports/slack, outbound delivery inintegrations/slack.gateway/— the standalone messaging gateway for inbound chat platforms (gateway/transports/telegram,gateway/transports/slack,gateway/core/session,gateway/core/storage). A peer ofsurfaces, not a child: the two never import each other.
Tier 2 — bootstrap
The composition root for process and harness wiring. Hosts (surfaces,
gateway) cannot import each other, and tools / integrations are peers that
must not import each other — yet registering harness adapters needs both
capability packages. bootstrap/ is the only package allowed to import tools
and integrations together.
Every entrypoint boots through configure_process(<profile>) from
bootstrap/process.py: a profile (CLI, gateway, web, scheduler worker,
embedded) selects which boot steps run, and the step order is fixed by this
package — profiles cannot invent a different sequence. Adapter and
scheduler-runner registration lives in bootstrap/adapters.py and nowhere
else; surfaces and gateway used to keep peer copies — do not reintroduce them.
Host-owned concerns stay out of this package: CLI/gateway logging, Rich product
ports, CLI’s update-tolerant Sentry init.
Tier 3 — tools and integrations
The capability layer — “do a thing against the outside world” — split by
responsibility:
integrations/— the boundary for user config and external clients: per-vendor config normalization, verification (verifier.py), API clients (client.py), the store/catalog that resolves credentials, and integration-local helpers. One folder per vendor (integrations/datadog,integrations/grafana,integrations/github, …) plus cross-cutting pieces likeintegrations/hermesandintegrations/llm_cli.tools/— the agent-callable boundary: every@tool(...)function andBaseToolsubclass, the tool registry, framework subsystems (tools/investigation,tools/interactive_shell),tools/system/for tools with no vendor in their domain purpose (fleet_monitoring,python_execution_tool,sre_guidance_tool,watch_dog), andtools/cross_vendor/for tools whose logic spans 2+ vendor integrations (fix_sentry_issue). See tool-placement-policy.md for the full decision rule, including when a tool belongs underintegrations/<vendor>/tools/instead. A tool is what the planner selects and the runtime executes.
integrations must never
import tools (or surfaces), so a vendor client never depends on the agent
layer and stays reusable on its own. The reverse edge is allowed and common — a
tool reaches an integration’s client for external data — so integrations
effectively sits one step below tools in the dependency graph. Do not
reintroduce top-level vendors/ or services/ packages — external-system code
belongs in integrations/, agent-callable code in tools/.
Tier 4 — core and platform
The shared runtime and cross-cutting services the capability layer is built on.
core/— the provider-agnostic agent runtime: the think → call tools → observe loop (core.agent.Agent), agent/investigation state (core/state) and context-budget enforcement (core/context_budget.py), the tool framework primitives (core/tool_framework), shared LLM clients (core/llm), agent-harness session handling (core/agent_harness), and pure domain rules (core/domain).platform/— cross-cutting services with no investigation logic of their own: guardrails, masking, sandbox, analytics, auth, notifications, observability, scheduler, and deployment. It deliberately shadows the stdlibplatformname and re-exposes it, soimport platformstill works.
core reaches platform
for guardrails, masking, observability, and evidence/log compaction, while
platform reaches back into core for the shared state and session types
(core.state, core.agent_harness.session). Splitting them into
separate tiers would forbid that edge, so they share a tier as siblings.
Tier 5 — config
The floor: shared constants, prompts, and UI theme. Everything above may
read from config, but config
imports no other first-party package — keeping it a leaf means constants can be
imported anywhere without dragging runtime along.
Cross-layer flows
Two worked examples showing how control descends the stack and results flow back up. Arrows only ever cross a boundary downward.An investigation from the CLI
surfaces/cliparses the command and hands off to the investigation capability intools/investigation— the surface never runs pipeline logic itself.tools/investigationdrives the six-stage pipeline (seeinvestigation-pipeline-architecture.md), askingcoreto run the ReAct loop and select/execute tools.- Evidence-gathering tools reach
integrationsfor vendor clients and resolved credentials;coreandplatformsupply the runtime, guardrails, and masking around every call. - The structured diagnosis flows back up to the surface, which owns how it is presented or delivered.
An inbound gateway message
gateway receives a message, resolves session state from its own storage, then
composes the same tier-3 capability code a surface would (after shared
bootstrap process boot) — without ever importing surfaces, since the two
are independent tier-1 peers.
Related docs
AGENTS.md— repo map and per-area “files to touch” guides.investigation-pipeline-architecture.md— how a single investigation runs end-to-end within thetools+corelayers.