Architecture
Purpose
Prismoid compiles human-authored harness manifests into a normalized Harness IR, then executes that IR against an objective using externally supplied implementations of model, context, capability, policy, verifier, environment, storage, and telemetry concerns.
The architecture follows hexagonal/ports-and-adapters design and the SOLID principles, but avoids turning every struct into an interface. Abstraction exists only at independently replaceable boundaries.
Dependency graph
prismoid-cli
/ | \
/ | \
adapters/models/storage/examples
\ | /
\ | /
prismoid-runtime
|
prismoid-harness
|
prismoid-ports
|
prismoid-types
Hard rules
typeshas no async runtime and no I/O.portscontains contracts only.harnessowns parsing/validation/compilation, not execution.runtimeowns the state machine, not concrete integrations.- adapters may depend on core crates; core crates never depend on adapters.
- the CLI is the composition root that wires concrete implementations together.
SOLID mapping
Single Responsibility
Each crate has one reason to change. Example: changing YAML parsing should not change runtime execution; changing the model provider should not change Harness IR.
Open/Closed
New model providers, context providers, capabilities, policies, verifiers, event sinks, and future environments are added by implementing ports rather than editing the runtime switch statement.
Liskov Substitution
A port implementation must preserve the semantic contract. For example, an EventSink may use
memory, JSONL, SQLite, or a remote service, but append must preserve event ordering supplied by
the runtime and must not silently mutate event meaning.
Interface Segregation
Ports are narrow. A verifier cannot invoke tools merely because a giant HarnessPlugin object
happens to expose them. If a verifier needs external data, it receives an explicit dependency in
its adapter implementation.
Dependency Inversion
The runtime depends on Model, ContextProvider, Capability, Policy, Verifier, and
EventSink traits. Concrete implementations depend on those abstractions.
Compile phase vs run phase
SOURCE PHASE EXECUTION PHASE
harness.yaml
│
▼
parse -> validate -> normalize
│
▼
resolve references (future)
│
▼
Harness IR + digest ──────────────────────┐
▼
validate bindings
│
▼
create run
│
┌──────────────────────┤
▼ │
resolve context │
▼ │
infer │
▼ │
proposed action │
▼ │
policy │
▼ │
execute │
└──────── observe ──────┘
│
final
▼
verify
/ \
fail pass
│ │
└── feedback ───┘
▼
complete
The runtime never executes YAML directly. Compilation creates a normalized data structure with a content digest. Future package resolution and inheritance happen before execution.
Event sourcing
Every meaningful state transition emits a semantic RunEvent. Events and OpenTelemetry serve
different purposes:
- Prismoid event: durable semantic history suitable for replay/audit.
- OTel span/event: operational telemetry suitable for latency/health analysis.
The scaffold includes memory and JSONL event sinks. SQLite/Postgres event stores are later adapters; they must not alter runtime semantics.
Why no Agent type
Agent tends to become a bag containing model, prompt, tools, memory, identity, and business
logic. Prismoid initially keeps those dimensions separate:
WHO -> future Actor/Agent layer
HOW -> Harness
WHAT -> Objective
WHERE -> Environment
THINK -> Model
TRUTH -> Verifier/Evidence
This makes harnesses independently composable and testable.