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

  • types has no async runtime and no I/O.
  • ports contains contracts only.
  • harness owns parsing/validation/compilation, not execution.
  • runtime owns 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.