OSPREY Design Philosophy
Working draft. Principles are still being collected and refined with the maintainer. The five below are confirmed. Each states a rule, its rationale, and the guidance an agent should apply. Principles are written generically: do not hard-code lists of current features or subsystems into this document — they go stale. Refer to "existing peer subsystems" and let the reader inspect the code.
OSPREY runs agentic AI in safety-critical control systems (accelerators, fusion experiments, beamlines), where an incorrect hardware write can damage equipment or endanger people. These principles exist to keep the framework trustworthy on real machines and adaptable as the field changes. Apply judgment: principles guide decisions, they are not mechanical rules to satisfy.
1. The safe state is the default
The cautious behavior is what happens unless configuration explicitly enables the riskier one.
- Hardware writes are disabled until config opts in; validation fails closed (e.g. an empty limits database blocks every write rather than allowing them); when configuration cannot be read, assume the safe path, not the convenient one.
- Wire safety in structurally, not per-call, so it cannot be forgotten. New connectors inherit the
writes-enabled guard automatically via
ControlSystemConnector.__init_subclass__(src/osprey/connectors/control_system/base.py). - A guard that currently never triggers is not dead code. It documents an invariant and protects against the day the assumption changes. Do not remove it.
2. Nothing facility-specific belongs in the core
Code in src/osprey must run unchanged at a different facility. Anything tied to one accelerator,
detector, or beamline lives behind a connector, in config, or in a preset/template — never in the
framework.
- Test each change against: "Would this be wrong at a different facility?" If yes, it is in the wrong layer.
- Facility values go in config; protocol differences go in connectors; facility narrative and agent wiring go in presets and templates.
- Do not create domain-prefixed sibling artifacts to hold facility variations. Fold the variation into the relevant preset or configuration and reuse the generic components.
3. Reach for symmetry, measured
When building a new subsystem, follow the structure that existing peer subsystems already use before inventing a new one. Consistency lowers design cost, shortens the learning curve, and makes later refactoring tractable because subsystems resemble each other.
- Look across the codebase first. If peers integrate through a subagent, an MCP server, and a service layer, a new subsystem of the same kind should do likewise unless it genuinely differs.
- This is a default, not a mandate. Divergence is allowed when a feature does not fit the existing shape, but it must be justified, not assumed. Forcing an ill-fitting feature into the common mold is as wrong as inventing a new pattern needlessly.
- This principle and Principle 4 both serve future changeability and can pull against each other — symmetry favors resembling neighbors, swappability favors not entangling with them. Balance them.
4. Keep components swappable
Separate a feature from the dependencies it relies on so either can be replaced independently. In a fast-moving field, tight coupling to a volatile dependency is technical risk, not convenience.
- Isolate the parts most likely to change — the model, the agent harness, external MCP/protocol standards — behind a boundary (interface, adapter, or config). Do not reference them inline throughout the codebase.
- Target state: the agent harness is a replaceable dependency. The current code does not yet meet this. New work moves toward it, not away from it.
5. A user-facing feature isn't done until it's discoverable
If a change alters what an operator or deployer sees or does, the user-facing surface ships with it: a
docs how-to, CLI --help text, and a changelog entry. Code that works but cannot be found is
incomplete, not done.
- Test each change against: "Could a user discover and use this without reading the source?" If no, the feature is unfinished.
- Match the documentation shape peers already use — if comparable features have a how-to page, this one does too.
- Internal-only or framework-internal changes are exempt; the bar is reader-facing impact, not line count.
How to apply
When a feature feels wrong but the reason is hard to name, identify which principle it violates and state it plainly: name the principle, point at the specific drift, and propose the change that brings the work back in line. The questions behind the principles: Is the unsafe path harder to reach than the safe one? Would this be wrong at another facility? Does this follow the shape of its peers? Can this dependency be swapped later without a rewrite? Could a user discover and use this without reading the source?