Spectre 0.2.6 is an OTP-native kernel for building conversational and
operational agents in Elixir. It is deliberately not a package that owns every
model, tool, memory store, browser, transport, and workflow. Those capabilities
live in focused libraries that integrate through explicit, versioned contracts.
This document explains the concepts shared by the ecosystem, what each library is for, how the libraries compose, and why Spectre is designed as a small core with optional packages instead of a monolithic framework.
The Shape Of The System
human channels other agents
│ │
▼ ▼
Spectre Beam Spectre Pulse
│ │
└──────────────────────┬────────────────────────────┘
▼
┌─────────────────────┐
│ Spectre core │
│ │
│ Flow / Turn / Run │
│ Instance / State │
│ Work / Vigil │
│ policy / lifecycle │
└─────────────────────┘
│ │ │
model choice │ │ │ memory context
▼ │ ▼
Spectre Prism │ Spectre Mnemonic
│
action plan │ mission continuity
▼ ▼
Spectre Kinetic Spectre Directive
│
▼
providers such as
Spectre Lens
The host application surrounds every layer with credentials, provider clients,
authorization, durable stores, supervision, and real side-effect boundaries.The dependency direction is one way: every ecosystem package depends on Spectre, while Spectre core does not depend on any ecosystem package. Satellite packages do not need one another at runtime. The complete set is loaded together only by ecosystem conformance tests.
The Core Concepts
Spectre keeps different kinds of state and work separate. That separation is what lets optional libraries compose without competing for ownership.
| Concept | Meaning | Owner |
|---|---|---|
Spectre.Stack | Immutable, validated package configuration for an Agent | application definition |
Spectre.Agent | Versioned behavior: routing, Flows, policies, operations, and package bindings | application code |
Spectre.Definition.Ref | Content identity of one portable canonical behavior envelope | Spectre core |
Spectre.Definition.Manifest | Sealed effective authority and execution closure for one Definition Ref | Spectre core + host ceiling |
Spectre.Definition.Store | Immutable publication and durable adapter boundary | host adapter |
Spectre.Definition.Resolver | Verified Definition, Manifest, receipt, contract, and drift resolution | Spectre core |
Spectre.Definition.Candidate | Immutable bootstrap proposal for one published Definition | trusted host + Spectre core |
Spectre.Instance.Activation | Generation-fenced current Definition snapshot | Spectre Instance sequencer |
Spectre.Event.Envelope | Ownership-fenced input, continuation, operation, and global event | Spectre Instance sequencer |
Spectre.Instance.Lifecycle | Independent admission, authority, retention, and activation axes | Spectre Instance sequencer |
Spectre.Skill.StateBinding | Generation- and branch-fenced private Skill state | Spectre Instance sequencer |
Spectre.Foundation.Conformance | Executable durable-format and Definition compatibility gate | Spectre core |
Spectre.Stack.Conformance | Whole-ecosystem package compatibility and collision gate | Spectre core + package tests |
Spectre.Projection.Audit | Exact deterministic view of a canonical Definition | Spectre core |
Spectre.Subject | Canonical application identity, independent of a channel identity | host application |
Spectre.Instance | Local canonical owner for one AgentRef + Subject | Spectre core |
Spectre.Turn | Public projection of the next observable conversational boundary | Spectre core |
Spectre.Run | Recoverable continuation for one conversational unit | Spectre core |
Spectre.Action | Closed proposal to call a declared capability | planner or deterministic Flow |
Spectre.Effect | Staged side effect with policy and lifecycle state | Spectre core |
Spectre.Invocation | Revision-fenced request to cross an external boundary | Spectre core |
Spectre.Work | Durable, bounded, multi-step operational procedure | Spectre core |
Spectre.Vigil | Durable recurring observation triggered by timers or events | Spectre core |
| Journal/checkpoint | Observable decisions and portable canonical recovery data | Spectre core plus host adapters |
There are two execution planes:
- The conversational plane uses Flow, Turn, and Run. It handles input, routing, replies, policy boundaries, and staged Effects.
- The operational plane uses Work and Vigil. It handles long procedures, retries, progress, pause/update/resume, timers, events, and restart recovery.
A Work is not a renamed Run, and a Directive mission is not a second Instance. Each abstraction has one job and one owner.
Which Library To Use
| Package | Use it when you need | It deliberately does not own |
|---|---|---|
spectre | Agent definitions, routing, policy, state, Runs, Instances, Work, Vigil, execution lifecycle, checkpoints | provider SDKs, credentials, business authorization, application side effects |
spectre_prism | Constraint-aware model/profile selection by purpose, modality, privacy, cost, latency, and capability, with optional bundled provider adapters | credentials, live provider sessions, provider scheduling, Run or Instance lifecycle |
spectre_kinetic | Tool retrieval and validated Action planning from Action Language | authorization or execution of the selected Action |
spectre_mnemonic | Active and durable memory, recall, search, consolidation, provenance, and subject-scoped context | canonical Agent state or the application database |
spectre_directive | A resumable mission and evolving-plan loop with correlated questions, invocations, and outcomes | general memory, tool discovery, core Work scheduling, or Instance ownership |
spectre_lens | Browser perception and browser Actions through backend-neutral protocols | browser authorization, core Effect lifecycle, or canonical process persistence |
spectre_beam | Human/external channel normalization and delivery, including Telegram and WhatsApp adapters | identity inference, application consent, policy, or automatic Effect execution |
spectre_pulse | Transport-independent Agent-to-Agent envelopes, discovery, routing, and delivery receipts | shared workflow state, global presence truth, or agent coordination policy |
The short distinctions are useful:
- Prism chooses which cognitive capability may answer.
- Kinetic chooses which declared action may be proposed.
- Lens supplies browser capabilities that a Flow or Kinetic may select.
- Mnemonic supplies recalled context, not canonical state.
- Directive advances one mission and its living plan.
- Work advances one durable operational controller.
- Beam crosses human or provider channel boundaries.
- Pulse crosses Agent-to-Agent protocol boundaries.
The Names
Ecosystem packages carry evocative names rather than descriptive ones, on purpose: a descriptive name such as "model-selection-middleware" goes stale the moment a package grows, while a metaphor stays stable. Learn them once — each name encodes the package's one job:
- Prism splits light into its components. Spectre Prism splits "call the model" into declared inference profiles and selects the one whose constraints — privacy, modality, cost, latency, depth — all hold.
- Kinetic is motion held ready. Spectre Kinetic turns declared application functions into a registry of candidate Actions and plans which one to propose. The energy becomes movement only when the core approves execution.
- Mnemonic is a memory aid, not the memory itself. Spectre Mnemonic recalls context for a turn; it never becomes canonical state.
- Directive is a mission order. Spectre Directive advances one mission and its living plan across many inputs.
- Lens is how the agent sees. Spectre Lens provides browser perception and browser actions, with everything it sees marked untrusted until explicitly converted.
- Beam carries signal between the agent and humans — and yes, it also runs on the BEAM. Spectre Beam normalizes provider channels such as Telegram and WhatsApp into Spectre input and delivery.
- Pulse is a signal between peers. Spectre Pulse moves versioned envelopes between agents over any transport.
The core vocabulary follows the same rule: a Vigil keeps watch (a durable observation loop between triggers); Work is exactly what it says (a bounded, terminating operational procedure); a Subject is who the work is about; an Instance is the process that owns both for that Subject.
Core 0.3.0 and satellite compatibility
The stable Spectre core is distributed through Hex:
{:spectre, "~> 0.3.0"}The satellite releases listed below belong to the historical 0.2.x release
train. They remain owned and versioned by their repositories and must not be
assumed compatible with core 0.3.0 until their manifests and adapter suites
say so. This separation is intentional: installing the new core never silently
upgrades another spectre_* package.
| Package | Release | Spectre requirement |
|---|---|---|
spectre | 0.3.0 | — |
spectre_beam | 0.2.0 | ~> 0.2.0 |
spectre_directive | 0.2.0 | ~> 0.2.0 |
spectre_kinetic | 0.2.0 | ~> 0.2.0 |
spectre_lens | 0.2.0 | ~> 0.2.0 |
spectre_mnemonic | 0.2.0 | ~> 0.2.0 |
spectre_prism | 0.2.0 | ~> 0.2.0 |
spectre_pulse | 0.2.0 | ~> 0.2.0 |
The core upgrade does not discard the recoverable historical contracts. Permanent State and Run fixtures continue to prove that existing persisted values remain readable where the public core contract promises it. Each satellite owns the equivalent proof for its durable and wire formats.
Applications that intentionally remain on the complete historical 0.2.x
stack must pin that stack consistently:
defp deps do
[
{:spectre, github: "elchemista/spectre", tag: "0.2.6"},
{:spectre_prism, github: "elchemista/spectre_prism", tag: "0.2.0"},
{:spectre_kinetic, github: "elchemista/spectre_kinetic", tag: "0.2.0"},
{:spectre_mnemonic, github: "elchemista/spectre_mnemonic", tag: "0.2.0"},
{:spectre_directive, github: "elchemista/spectre_directive", tag: "0.2.0"},
{:spectre_lens, github: "elchemista/spectre_lens", tag: "0.2.0"},
{:spectre_beam, github: "elchemista/spectre_beam", tag: "0.2.0"},
{:spectre_pulse, github: "elchemista/spectre_pulse", tag: "0.2.0"}
]
endAn application rarely needs all seven satellite packages. Adding one library does not silently enable the others.
Compose Packages With A Stack
Spectre.Stack is the composition boundary. Each install block is parsed by
that package, so package-specific words such as model, isolate_by,
backend, and channel do not become global framework macros.
defmodule MyApp.AI do
use Spectre.Stack, id: :my_app
install Spectre.Prism, max_attempts: 2 do
provider(:openrouter, MyApp.OpenRouter)
model(:fast, id: "small-model")
model(:deep, id: "reasoning-model")
purpose(:route_classification, prefer: :fast)
default(:deep)
end
install Spectre.Kinetic,
mode: :closed_moves,
actions: MyApp.Actions,
modes: [create_report: :write]
install Spectre.Mnemonic, namespace: :my_app do
isolate_by([:agent, :subject, :conversation])
end
install(Spectre.Directive, turn_handler: true)
install Spectre.Lens, planner_exposure: [:look, :discover] do
backend(SpectreLens.Browsers.Lightpanda,
instances: 2,
protocol: SpectreLens.Protocol.Lightpanda
)
end
install Spectre.Beam do
channel(:telegram,
type: :telegram,
adapter: Spectre.Beam.Adapters.ExGram,
before_decode: [MyApp.VerifyTelegramSignature]
)
end
install Spectre.Pulse do
transport(:local, Spectre.Pulse.Transports.Local)
directory(MyApp.AgentDirectory)
end
endBind the Stack once:
defmodule MyApp.Agent do
use Spectre.Agent, stack: MyApp.AI
router(via: [:regex, :classifier])
flow :analysis, prism: [minimum: :deep] do
on :EXPLAIN, regex: ~r/^explain\b/i do
reason(:explain)
end
end
flow :browser do
on :INSPECT, regex: ~r/^inspect\b/i do
action({:lens, :look},
args: %{
url: "https://example.com",
opts: [include: [:markdown, :links]]
},
mode: :read
)
end
end
flow :notify_human do
on :NOTIFY_HUMAN, regex: ~r/^notify human$/i do
beam(:owner, via: :telegram, text: "The report is ready")
end
end
flow :notify_agent do
on :NOTIFY_AGENT, regex: ~r/^notify agent$/i do
pulse(:researcher,
act: :inform,
type: "report.ready",
data: %{report_id: "report-42"}
)
end
end
endSelecting a Stack activates package adapters, but activation is not authorization. It does not make every action planner-visible, approve a protected Effect, authenticate a channel identity, or start every runtime resource.
Run The Boundaries Explicitly
Live processes and secrets enter at runtime, not in the compiled Stack:
{:ok, stack_runtime} =
Spectre.Stack.start_link(MyApp.AI,
packages: [lens: [binary: "/opt/lightpanda"]]
)
{:ok, instance} =
Spectre.instance(MyApp.SpectreSupervisor, MyApp.Agent, account_id)
{:ok, turn} =
Spectre.turn(instance, "inspect the release page",
stack_runtime: stack_runtime
)
case turn.decision do
{:awaiting, awaitable, result} ->
MyApp.UI.present_approval(awaitable, result)
{:needs, _effect, result} ->
Spectre.execute(instance, result, stack_runtime: stack_runtime)
{:reply, result} ->
MyApp.Delivery.reply(result.reply_text)
{:completed, outcome, _result} ->
MyApp.Operations.record(outcome)
{:no_response, _result} ->
:ok
endThe host decides whether and where to execute. Spectre owns the deterministic lifecycle around that decision; the package owns only its capability boundary.
Spectre Core
Use the core alone when routing, state, policy, actions, or operational loops are sufficient.
defmodule MyApp.SupportAgent do
use Spectre.Agent
actions(MyApp.SupportActions)
policy :confirm_delete do
request(:confirm_delete)
accept(:confirmed, regex: ~r/^yes$/i)
reject(:cancelled, regex: ~r/^no$/i)
end
protect(:delete_account, with: :confirm_delete)
flow :support do
on :HELP, regex: ~r/^help$/i do
reply("How can I help?")
end
on :DELETE_ACCOUNT, regex: ~r/^delete my account$/i do
action(:delete_account)
end
end
endUse reason for inference that must not plan actions, act when action
planning is intentional, and an explicit action when the Flow already knows
the capability. Use Work for a bounded procedure that must survive beyond a
Turn:
{:ok, work_ref, _view} =
Spectre.start_work(instance, MyApp.ExportReport, %{report_id: "report-42"})
{:ok, view} = Spectre.loop(instance, work_ref)
{:ok, paused} = Spectre.pause_loop(instance, work_ref)
{:ok, resumed} = Spectre.resume_loop(instance, work_ref)Spectre Prism: Select The Cognitive Capability
Prism turns a set of model declarations into inference profiles and selects a profile only when every requested constraint is satisfied.
install Spectre.Prism, max_attempts: 2 do
provider(:openrouter, MyApp.OpenRouter)
model(:fast, id: "small-model")
model(:deep, id: "reasoning-model")
purpose(:route_classification, prefer: :fast)
default(:deep)
selector(Spectre.Prism.Selector.Adaptive)
endUse Prism when several inference capabilities differ in privacy, modality, context window, latency, cost, or depth. Prism ships optional adapters for OpenAI, OpenRouter, Ollama, and Gemini behind an injectable HTTP transport; credentials are resolved only at runtime, and live provider sessions remain caller-owned and outside the compiled Stack. Prism selects; Spectre performs the inference step and owns the Run boundary.
Spectre Kinetic: Plan Closed Actions
Kinetic extracts a registry of declared application functions, retrieves a small candidate set, maps typed slots, and returns a provider-neutral Action.
defmodule MyApp.Actions do
use SpectreKinetic
@al ~s(CREATE REPORT WITH: ACCOUNT="acme")
@doc "Creates a report for an account."
@spec create_report(String.t()) :: {:ok, map()} | {:error, term()}
def create_report(account), do: MyApp.Reports.create(account)
end
defmodule MyApp.ReportingAgent do
use Spectre.Agent
use Spectre.Kinetic, actions: MyApp.Actions, modes: [create_report: :write]
flow :reports do
on :CREATE_REPORT, regex: ~r/^create report/i do
act(:create_report)
end
end
endKinetic does not call create_report/1 while planning. Spectre stages the
Action as an Effect, applies policy, persists lifecycle state, and crosses the
provider boundary only after explicit execution.
Spectre Mnemonic: Recall Context Without Replacing State
Mnemonic provides hot memory, durable stores, hybrid search, graph associations, consolidation, freshness, contradictions, and provenance.
install Spectre.Mnemonic, namespace: :support do
isolate_by([:agent, :subject, :conversation])
endWith the Stack bound, Spectre's normal memory hooks recall context before a Run advances and remember a compact projection after commit. Standalone APIs remain useful too:
{:ok, _packet} =
SpectreMnemonic.remember("Acme prefers weekly PDF reports",
scope: {:account, "acme"},
persist?: true
)
{:ok, recalled} =
SpectreMnemonic.recall("report preferences", scope: {:account, "acme"})Memory can be stale, ranked, compacted, or forgotten. Canonical Agent state is revisioned and authoritative. Keeping those concepts separate prevents a retrieval result from silently becoming system truth.
Spectre Directive: Advance A Mission And Living Plan
Directive is useful when a mission can ask for information, request an invocation, revise a guided plan, and continue across several inputs.
alias SpectreDirective.Request
{:ok, directive} =
Spectre.Directive.new(
mission: "Research Acme",
success: "Return a sourced summary",
mode: :autonomous
)
{:request, %Request{kind: :reason} = request, directive} =
Spectre.Directive.next(directive)
{:request, next_request, directive} =
Spectre.Directive.respond(
directive,
request.id,
{:propose_plan, [%{id: "research", title: "Research Acme"}]}
)The pure API performs no model call or side effect. The host resolves each correlated request. Directive also offers an optional supervised runtime and an opt-in Spectre turn handler.
Choose Directive for a mission whose plan is part of the domain. Choose core Work for a bounded operational controller with attempts, budgets, retries, pause/update/resume, progress, and canonical recovery. A Directive may start or supervise application work through explicit boundaries, but it does not replace Work or the Instance.
Spectre Lens: Perceive And Act In A Browser
Lens separates browser lifecycle from agent-readable page semantics. It can start Lightpanda, connect to a remote CDP endpoint, produce untrusted views, discover goal-relevant regions, perform browser actions, and export artifacts.
{:ok, lens} = SpectreLens.open(instances: 2)
{:ok, tab} = SpectreLens.new_tab(lens, url: "https://example.com")
{:ok, view} =
SpectreLens.look(tab,
include: [:markdown, :semantic_tree, :interactive, :forms, :links]
)
{:ok, safe_context} = SpectreLens.agent_context(view)
:ok = SpectreLens.act(tab, {:click, ref: "button[type=submit]"})
:ok = SpectreLens.close(lens)Top-level projections are marked trust: :untrusted; convert them with
agent_context/2 before inserting browser content into a model prompt. In a
Stack, browser PIDs remain inside an explicitly started runtime and only
portable TabRef values may cross a Spectre checkpoint.
Spectre Beam: Connect Human And Provider Channels
Beam normalizes provider events into Spectre input and delivers replies through the same endpoint. It preserves conversation affinity and exposes plug stages for authentication, enrichment, filtering, redaction, and telemetry.
install Spectre.Beam do
channel(:telegram,
type: :telegram,
adapter: Spectre.Beam.Adapters.ExGram,
before_decode: [MyApp.VerifyTelegramSignature],
inbound_pipeline: [MyApp.EnrichSender]
)
end
{:ok, exchange} =
Spectre.Beam.handle(MyApp.Agent, :telegram, raw_update,
adapter_opts: [client: telegram_client]
)For subject-scoped continuity, the host authenticates the provider principal,
binds its Spectre.ExternalIdentity to a canonical Subject, then calls
handle_instance/5. Beam never guesses identity from a name, phone number,
conversation id, message, or model result.
Reactive delivery and proactive delivery are different boundaries. A
proactive beam(...) handler stages an Effect; it does not send automatically.
Spectre Pulse: Communicate Between Agents
Pulse gives agents a versioned envelope whose meaning is independent of local mailboxes, PubSub, WebSocket, REST, distributed Erlang, or a custom transport.
defmodule MyApp.Researcher do
use Spectre.Agent
use Spectre.Pulse
pulsing do
identity("spectre://acme/researcher")
advertise(capabilities: [:research])
end
flow :remote_requests do
on :RESEARCH, pulse: "research.perform" do
run(:research)
end
end
def research(input, _context), do: "accepted: #{input.text}"
endA sender names a logical contact and stages the protocol Effect:
pulse(:researcher,
act: :request,
type: "research.perform",
data: %{topic: "nautical market"},
expect: "research.completed"
)Pulse owns envelope validation, address resolution, route selection, and a technical receipt. It does not promise exactly-once delivery, share Agent state, or decide whether the recipient should accept the request.
Common Compositions
| Application need | Suggested packages |
|---|---|
| Deterministic conversational service | core only |
| Provider-aware assistant with long-term context | core + Prism + Mnemonic |
| Safe tool-using agent | core + Kinetic; add Prism for model selection |
| Browser research agent | core + Lens + Kinetic + Prism; add Mnemonic for recall |
| Resumable guided mission | core + Directive; add Mnemonic for recalled context |
| Telegram or WhatsApp assistant | core + Beam; add Prism/Mnemonic as needed |
| Cooperative agents across transports | core + Pulse |
| Long-running monitored automation | core Work/Vigil plus only the capability packages its operations need |
Start with the core and one concrete need. Add a package when its boundary is clear; do not install the entire ecosystem as a default preset.
Why Spectre Was Built This Way
Agent systems combine several hard problems: conversation, durable operations, inference, memory, planning, tools, browsers, human channels, agent protocols, identity, and authorization. Putting all of them in one framework object makes the first demo short, but it also tends to make ownership and failure behavior implicit.
Spectre chooses a different tradeoff for five reasons.
One canonical owner
The Instance owns canonical state and lifecycle. A memory package cannot quietly become state, a transport cannot become a scheduler, and a planner cannot become an executor. Crash recovery and concurrency are tractable because there is one place where a transition becomes authoritative.
Capabilities are explicit and replaceable
A Stack says exactly which packages exist. Logical refs and package-local DSLs avoid a global registry where unrelated libraries can overwrite one another. Applications can replace Prism, Kinetic, Lens, memory, or transport adapters without rewriting the core Agent model.
Proposal is separate from authority
Models and planners may propose an Action. Policy and deterministic lifecycle code decide whether it becomes executable. The host still controls credentials and the final side-effect boundary. This avoids the common shortcut where generating a tool call also grants permission to run it.
Portable data is separate from live infrastructure
Definitions, Runs, and checkpoints contain portable values. PIDs, sockets, browser sessions, provider clients, and secrets stay in supervised runtime resources supplied by the host. A restart therefore restores intent and lifecycle without pretending that a dead connection was durable.
Applications pay only for what they use
The core does not force browser, model-provider, vector-search, transport, or channel SDKs into every deployment. Each library can evolve and test its own public surface while the core remains focused on the contracts that require a single authority.
This is not separation for its own sake. The Stack still gives applications one place to compose capabilities, and Agent extensions remove repetitive glue. The difference is that composition stays visible, versioned, and reversible.
The compass: OTP and the actor model
Spectre's design questions are answered by one compass: what is the OTP shape of this problem? The mapping is already direct, and future concepts will keep converging on it rather than drifting toward framework-specific abstractions:
| Spectre concept | Actor-model ancestor |
|---|---|
| Instance | a process that owns its state and serializes writes through its mailbox |
| Runner | a supervised one-shot task: one attempt, isolated crash, no shared state |
| Vigil | a timer- and event-driven loop that holds no live process while waiting |
| Effect / Invocation | an explicit message across a boundary, never a hidden call |
| Checkpoint | what a restart restores: intent and lifecycle, not dead connections |
| Pulse envelope | location-transparent message passing without shared state |
A new capability enters the ecosystem the same way every time: find the single owner, make every boundary a message, make recovery a restart. If a proposed feature has no clean actor-model shape — shared mutable state, ambient authority, a hidden synchronous call — that is evidence against the feature, not against the model.
Rules For Ecosystem Packages
An ecosystem package should preserve these boundaries:
- Depend on Spectre; never make Spectre core depend on the package.
- Publish a
Spectre.Stack.Installablemanifest with a precise Spectre requirement and portable metadata. - Keep package DSL names inside the package's
installblock. - Put runtime clients, processes, credentials, and secrets behind explicit host-owned runtime options or supervised resources.
- Stage Effects and Actions; do not bypass core policy or lifecycle.
- Re-resolve live adapters after restore instead of serializing their handles.
- Fail closed on unknown identities, capabilities, schemas, or ambiguous side-effect outcomes.
- Maintain a normative public API manifest and permanent compatibility fixtures for persisted or wire formats.
These rules are what make a collection of libraries an ecosystem rather than a bundle of unrelated integrations.
Validate The Sibling Repositories
Each package accepts SPECTRE_PATH for local compatibility testing. Pulse also
accepts paths for all satellite packages and runs the complete seven-package
Stack conformance test:
SPECTRE_PATH=../spectre \
SPECTRE_BEAM_PATH=../spectre_beam \
SPECTRE_DIRECTIVE_PATH=../spectre_directive \
SPECTRE_KINETIC_PATH=../spectre_kinetic \
SPECTRE_LENS_PATH=../spectre_lens \
SPECTRE_MNEMONIC_PATH=../spectre_mnemonic \
SPECTRE_PRISM_PATH=../spectre_prism \
mix test
Run that command from spectre_pulse. Run SPECTRE_PATH=../spectre mix test
from every other sibling repository. The path dependency must report version
0.2.0; a stale ~> 0.1.x requirement is an incompatibility, even if the
source code would otherwise compile.