Sprint 1: project skeleton, type system, and all architecture specs

- src/types.hpp: complete UCWM type system in C++20 — 19 enums, 11 facet
  data types, all core structs (CanonicalObject, Constraint, Facet,
  GateSignal, WorldState, etc.) with full JSON round-trip serialization
- src/main.cpp: smoke test — constructs apple-problem WorldState by hand,
  serializes to JSON
- tests/test_types.cpp: 19 tests, 123 assertions, all passing
- CMakeLists.txt: CMake + CPM build with nlohmann/json, spdlog, Catch2
- schemas/: JSON Schema contracts for all UCWM data types
- gates/, specialists/, resolver/, synthesis/: language-agnostic interface
  contracts and domain specs for all pipeline layers
- docs/: architecture, vocabulary, decision matrices, roadmap (6 phases,
  28 sprints), sprint_001, implementation_constraints

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 16:09:55 -07:00
commit b758d7ea60
35 changed files with 6344 additions and 0 deletions

136
specialists/contract.md Normal file
View File

@@ -0,0 +1,136 @@
# Specialist Interface Contract
A specialist is a contract-bound module that inspects canonical objects within a specific relation domain and emits typed facets and constraints. Specialists are the workhorses of UCWM — they build the structured world state that the resolver stabilizes.
---
## Design principles
1. **Bounded output.** A specialist emits a finite list of Facets and Constraints. It does not emit free text, tokens, or embeddings as primary output.
2. **Contract-typed.** Every output conforms to the schemas in `schemas/facets.json` and `schemas/constraints.json`.
3. **Domain-limited.** A specialist operates only in its declared domain. The temporal specialist does not emit ownership constraints.
4. **Implementation-neutral.** A specialist may be neural, statistical, symbolic, rule-based, graph-based, or hybrid. The contract is identical regardless.
5. **Replaceable.** Any specialist can be replaced with a different implementation as long as the new implementation satisfies this contract. No other component should be affected.
---
## Input
```
SpecialistInput {
specialist_id: string -- which specialist is being invoked
target_refs: string[] -- object_ids to inspect
world_state: WorldState -- current state (read-only for this specialist)
routing_context: SpecialistAssignment -- from the RoutingDecision
budget: BudgetSpec -- resource limits
}
```
The specialist receives a read-only view of WorldState. It MUST NOT modify WorldState directly. It only returns outputs; the resolver applies them.
---
## Output
```
SpecialistOutput {
specialist_id: string
new_facets: Facet[]
new_constraints: Constraint[]
updated_objects: ObjectUpdate[] -- confidence or alias updates to existing objects
provenance: Provenance[]
budget_used: BudgetUsed
}
ObjectUpdate {
object_id: string
confidence_delta: float? -- adjustment to existing confidence
new_aliases: string[]?
new_status: string? -- only "proposed" → "active" transitions
}
BudgetUsed {
objects_inspected: integer
constraints_emitted: integer
iterations_used: integer
}
```
---
## Invariants
Every specialist implementation MUST satisfy all of the following:
1. **Domain adherence.** A specialist only emits facets and constraints of kinds it declares in its manifest. A temporal specialist emits `temporal` facets and temporal constraints (`before`, `after`, `during`). It does not emit `ownership` facets.
2. **Valid refs.** Every `argument_ref` in a constraint and every `object_ref` in a facet must reference an ID that exists in the input WorldState or in `new_objects` returned in the same output. No dangling references.
3. **Bounded by budget.** The specialist respects its `BudgetSpec`. If `max_constraints` is 10, it returns at most 10 constraints.
4. **Confidence required.** Every Facet and Constraint emitted must have a `confidence` value.
5. **Source module declared.** Every Facet, Constraint, and Provenance record must have `source_module` set to the specialist's ID.
6. **Provenance required.** For every Constraint and Facet emitted, there must be a corresponding Provenance record in `provenance[]`.
7. **No WorldState mutation.** The specialist only returns outputs. It does not write to WorldState.
8. **Idempotent.** Calling a specialist twice with identical input produces outputs that are logically equivalent (same constraint set, possibly different IDs if IDs are randomly generated — use deterministic IDs where possible).
---
## Specialist manifest format
Each specialist declares its capabilities in a manifest:
```
SpecialistManifest {
specialist_id: string
display_name: string
description: string
declared_facet_kinds: string[] -- facet kinds this specialist may emit
declared_constraint_types: string[] -- constraint types this specialist may emit
trigger_gates: string[] -- gate_ids that commonly activate this specialist
implementation: string -- reference to implementation
version: string
}
```
---
## Specialist output validation
Before the resolver applies specialist outputs, a contract validator MUST check:
- All emitted facets have `facet_kind` in `declared_facet_kinds`
- All emitted constraints have `constraint_type` in `declared_constraint_types`
- All `argument_refs` and `object_refs` resolve to existing or co-emitted objects
- All `confidence` values are in [0, 1]
- Budget limits were not exceeded
- `source_module` is correct on all records
A specialist that emits invalid output is rejected. The resolver logs the rejection and continues without that specialist's output.
---
## Registered specialists
| specialist_id | Declared facet kinds | Key constraint types |
|---|---|---|
| `entity` | `syntactic`, `semantic` | `same_entity`, `not_same_entity` |
| `temporal` | `temporal` | `before`, `after`, `during` |
| `spatial` | `spatial` | `inside`, `outside`, `adjacent` |
| `causal` | `causal` | `causes`, `enables`, `blocks` |
| `quantity` | `quantity` | `quantity_equals`, `quantity_sum`, `quantity_difference` |
| `logic` | `logical` | `truth_value`, `implies`, `negates`, `contradicts` |
| `ownership` | `ownership` | `owns`, `transfers` |
| `social` | `social` | `has_role`, `believes`, `obligated` |
| `planning` | `planning` | `prerequisite`, `goal` |
| `code` | `code_structure` | domain-specific |
See individual specialist files for full manifests and domain logic.

146
specialists/entity.md Normal file
View File

@@ -0,0 +1,146 @@
# Entity Specialist
**specialist_id:** `entity`
**trigger_gates:** `has_entity_reference`, `has_coreference`
---
## Responsibility
The entity specialist finds named and implied entities, proposes CanonicalObjects for them, resolves aliases, and detects coreference chains. It upgrades `proposed` objects to `active` when evidence is sufficient, and emits `same_entity` or `not_same_entity` constraints when coreference is resolved.
---
## Manifest
```
declared_facet_kinds:
- syntactic
- semantic
declared_constraint_types:
- same_entity
- not_same_entity
- has_role
budget_caps:
max_objects_proposed: 10
max_facets_emitted: 8
max_constraints_emitted: 12
max_merges: 4
max_splits: 0
```
---
## Input focus
Given `target_refs`, the entity specialist inspects:
- Surface text spans that evidence each target object
- Pronouns and alias spans in the same context window
- Prior objects already in WorldState with overlapping spans or labels
---
## Output: CanonicalObject proposals
The entity specialist may propose new objects of kind `entity`. New entity proposals use `status: proposed`. Proposals are confirmed (`active`) only if confidence exceeds 0.75.
Required fields on all proposed entities:
- `surface_span`
- `canonical_label`
- `aliases` (may be empty)
- `confidence`
- `source_module: entity`
---
## Output: SyntacticFacet
For each confirmed entity, emit a `SyntacticFacet` describing its grammatical role in the input.
```
SyntacticFacet fields used by entity specialist:
- grammatical_role: "subject" | "object" | "indirect_object" | "adjunct" | "other"
- head_token: string
```
---
## Output: Constraints
### `same_entity`
Emit when two object proposals likely refer to the same real-world entity. Common cases:
- Pronoun resolution: `she``Alice`
- Alias resolution: `the company``Acme Corp`
- Repeated reference: `Alice ... Alice` (same span, different positions)
```
Constraint example:
{
"constraint_type": "same_entity",
"argument_refs": ["E1", "E3"],
"polarity": "positive",
"strength": "hard",
"expression": "same_entity(E1, E3)"
}
```
### `not_same_entity`
Emit when two object proposals are clearly different entities that might otherwise be confused.
```
Constraint example:
{
"constraint_type": "not_same_entity",
"argument_refs": ["E1", "E2"],
"polarity": "positive",
"strength": "hard",
"expression": "not_same_entity(Alice, Bob)"
}
```
### `has_role`
Emit when an entity is playing a specific functional role in the input context (giver, receiver, buyer, seller, etc.).
```
Constraint example:
{
"constraint_type": "has_role",
"argument_refs": ["E1", "EV_transfer"],
"polarity": "positive",
"strength": "soft",
"expression": "has_role(Alice, giver, EV_transfer)"
}
```
---
## Coreference resolution algorithm (rule-based baseline)
```
for each pronoun P in input:
candidates = all active entity objects in WorldState
rank candidates by:
1. recency (closest preceding entity)
2. gender agreement (he/she/they match)
3. number agreement (they/them = plural)
4. animacy (personal pronouns prefer animate entities)
if top candidate confidence > 0.7:
emit same_entity(pronoun_object, top_candidate)
else:
propose new entity with surface_span = P, status = proposed
```
---
## Contract test requirements
- Given `Alice gave Bob 2 apples`, must emit at least two distinct entity objects (Alice, Bob)
- Must emit `not_same_entity(Alice, Bob)`
- Given `She gave him 2 apples` with prior context establishing Alice and Bob, must resolve `she → Alice` and `him → Bob`
- Must not emit quantity or temporal constraints
- All emitted objects must have `source_module: entity`

123
specialists/logic.md Normal file
View File

@@ -0,0 +1,123 @@
# Logic Specialist
**specialist_id:** `logic`
**trigger_gates:** `has_logical_negation`, `has_comparison`, `has_constraint`
---
## Responsibility
The logic specialist handles negation, implication, contradiction, quantifiers, and truth status. It attaches LogicalFacets to propositions and claims, and emits logical constraints that the resolver uses to detect contradictions and propagate truth values.
---
## Manifest
```
declared_facet_kinds:
- logical
declared_constraint_types:
- truth_value
- implies
- negates
- contradicts
- requires
- exclusive_or
- universally_quantified
- existentially_quantified
budget_caps:
max_objects_proposed: 4
max_facets_emitted: 8
max_constraints_emitted: 10
max_merges: 0
max_splits: 0
```
---
## Output: LogicalFacet
Attach to objects of kind `proposition`, `claim`, or `event` with a truth dimension.
```
LogicalFacet fields used:
- truth_status: "true" | "false" | "unknown" | "hypothetical" | "negated" | "asserted"
- quantifier: "universal" | "existential" | "none"
- implies_refs: [proposition_ids this implies]
- negates_refs: [proposition_ids this negates]
- contradicts_refs: [proposition_ids this contradicts]
```
---
## Output: Constraints
### `truth_value(P, status)`
Sets the explicit truth status of a proposition.
```
{
"constraint_type": "truth_value",
"argument_refs": ["P1"],
"polarity": "positive",
"strength": "hard",
"expression": "truth(P1) = false"
}
```
### `negates(P1, P2)`
P1 is the logical negation of P2. If P1 is true, P2 is false, and vice versa.
### `implies(P1, P2)`
If P1 is true, P2 must be true. Defeasible by default unless marked hard.
### `contradicts(P1, P2)`
P1 and P2 cannot both be true. Strength is always hard.
### `requires(A, B)`
A requires B to hold. Equivalent to `implies(A, B)` but semantically marks a dependency rather than a logical implication.
### `exclusive_or(P1, P2)`
Exactly one of P1 or P2 is true.
---
## Negation handling
Negation is the most error-prone logical operation. Rules:
1. Surface negation (e.g., `not`, `isn't`) does not always mean logical negation of a proposition — it may be just semantic modification.
2. When `has_logical_negation` fires, the logic specialist inspects whether the negation applies to:
- A predicate (`Alice is NOT happy` → LogicalFacet truth_status = negated)
- A quantifier (`NOT all → some are not`)
- A condition (`unless X` → implies(NOT X, consequence))
3. Emit `truth_value` constraints conservatively — only when confident the scope of negation is correctly identified.
---
## Contradiction detection support
The logic specialist does not resolve contradictions. It emits `contradicts` constraints when it detects that two propositions in WorldState logically oppose each other. The resolver handles contradiction resolution.
```
if P1 has truth_status = true AND P2 = negation_of(P1):
emit contradicts(P1, P2) with strength = hard
```
---
## Contract test requirements
- Given `Alice is not happy`, must emit a LogicalFacet on `P_alice_happy` with `truth_status: negated`
- Given `If it rains, the ground is wet`, must emit `implies(E_rain, S_ground_wet)`
- Must not emit temporal or quantity constraints
- Contradiction constraints must reference two objects that exist in WorldState
- All emitted constraints must have `source_module: logic`

132
specialists/quantity.md Normal file
View File

@@ -0,0 +1,132 @@
# Quantity Specialist
**specialist_id:** `quantity`
**trigger_gates:** `has_quantity`, `is_math`
---
## Responsibility
The quantity specialist extracts numeric values, attaches QuantityFacets to quantity objects, and emits arithmetic and comparison constraints. When arithmetic can be resolved deterministically, it resolves it. When resolution requires a solver, it emits a structured constraint for the resolver.
---
## Manifest
```
declared_facet_kinds:
- quantity
declared_constraint_types:
- quantity_equals
- quantity_sum
- quantity_difference
- quantity_product
- quantity_quotient
- quantity_greater
- quantity_less
- quantity_at_least
- quantity_at_most
- quantity_bound
budget_caps:
max_objects_proposed: 6
max_facets_emitted: 6
max_constraints_emitted: 10
max_merges: 0
max_splits: 0
```
---
## Output: QuantityFacet
Attach to any object of kind `quantity`.
```
QuantityFacet fields used:
- value: number or symbolic expression
- unit: string (apples, dollars, meters, etc.)
- is_exact: true/false
- derived_from_refs: quantity object IDs this was computed from
```
---
## Output: Constraints
### `quantity_equals(Q_result, expression)`
Emit when a quantity is defined as equal to an expression.
```
{
"constraint_type": "quantity_equals",
"argument_refs": ["Q_alice_final", "Q_expr_5_minus_2"],
"expression": "quantity(Q_alice_final) = quantity(Q1) - quantity(Q2)",
"strength": "hard"
}
```
### `quantity_difference(Q_result, Q_minuend, Q_subtrahend)`
Preferred form when the specific operation is subtraction.
```
argument_refs order: [result, minuend, subtrahend]
```
### `quantity_sum(Q_result, Q_addend1, Q_addend2, ...)`
Result equals the sum of all argument quantities.
### `quantity_greater(Q_a, Q_b)`
Q_a > Q_b.
### `quantity_less(Q_a, Q_b)`
Q_a < Q_b.
---
## Arithmetic resolution
When all inputs to an arithmetic constraint are exact numeric values, the quantity specialist SHOULD resolve the arithmetic inline and set the result quantity's `value` directly.
```
if quantity_difference(Q_result, Q1, Q2):
if Q1.value is exact numeric AND Q2.value is exact numeric:
Q_result.value = Q1.value - Q2.value
Q_result.is_exact = true
mark constraint as resolved
else:
emit constraint with status = unresolved
resolver handles it
```
This avoids unnecessary round-trips through the resolver for simple arithmetic.
---
## Unit tracking
Units must be tracked and propagated. Emitting a `quantity_difference` constraint without units is not sufficient — the result unit must be declared or inferred.
Rules:
- Same unit on both operands: result inherits unit
- Different units on operands: emit a `quantity_bound` constraint marking the result as unit-ambiguous until a converter is applied
- Unitless operands: result is unitless
---
## Contract test requirements
- Given `Alice had 5 apples. She gave Bob 2 apples.`:
- Must emit Q1 with value=5, unit="apples"
- Must emit Q2 with value=2, unit="apples"
- Must emit `quantity_difference(Q_alice_final, Q1, Q2)` or `quantity_equals(Q_alice_final, "5-2")`
- Must set Q_alice_final.value = 3 (inline resolution)
- Must not emit temporal or entity constraints
- All quantity facets must have `unit` set or explicitly `"unitless"`
- All emitted constraints must have `source_module: quantity`

107
specialists/spatial.md Normal file
View File

@@ -0,0 +1,107 @@
# Spatial Specialist
**specialist_id:** `spatial`
**trigger_gates:** `has_spatial_relation`
---
## Responsibility
The spatial specialist adds location, containment, direction, proximity, and topology constraints to objects. It attaches SpatialFacets to entities and events with spatial dimension, and emits spatial constraints between objects.
---
## Manifest
```
declared_facet_kinds:
- spatial
declared_constraint_types:
- inside
- outside
- adjacent
- above
- below
- north_of
- south_of
- east_of
- west_of
- near
- far
- at_location
- between
- on_path
budget_caps:
max_objects_proposed: 4
max_facets_emitted: 8
max_constraints_emitted: 12
max_merges: 0
max_splits: 0
```
---
## Output: SpatialFacet
Attach to objects with a spatial dimension (entities, events, states, code objects with file location).
```
SpatialFacet fields used:
- location_label: string (natural language location)
- coordinates: {x, y, z} if numeric coords available
- containment_refs: constraint IDs for inside/outside relations
- reference_frame: coordinate system or reference object
```
---
## Output: Constraints
### `inside(A, B)`
Object A is contained within object B. Example: `inside(book, shelf)`, `inside(Alice, building)`.
### `adjacent(A, B)`
A and B are next to each other without containment.
### `at_location(A, L)`
Object A is at named location L. L should be a canonical object of kind `entity` (a place).
### `between(A, B, C)`
Object A is between objects B and C.
### Directional constraints
`north_of`, `south_of`, `east_of`, `west_of`, `above`, `below` — all take two argument_refs in the form `(subject, reference)`.
### `near(A, B)` / `far(A, B)`
Relative proximity. Strength is always `soft` since "near" is inherently vague without a defined scale.
---
## Reference frame handling
Spatial constraints are only meaningful relative to a reference frame. When no frame is explicit in the input, use `"world"` as the default frame label.
```
SpatialFacet.reference_frame:
- "world" -- global geographic frame (default when unspecified)
- "local" -- relative to a locally established reference
- "{object_id}" -- relative to a specific object in WorldState
```
---
## Contract test requirements
- Given `Alice is in the kitchen`, must emit `at_location(E_alice, O_kitchen)` or `inside(E_alice, O_kitchen)`
- Given `The box is on the shelf`, must emit `above(O_box, O_shelf)` or `on(O_box, O_shelf)`
- Must not emit temporal or quantity constraints
- All spatial facets must have `location_label` set
- All emitted constraints must have `source_module: spatial`

117
specialists/temporal.md Normal file
View File

@@ -0,0 +1,117 @@
# Temporal Specialist
**specialist_id:** `temporal`
**trigger_gates:** `has_temporal_relation`, `has_state_change`
---
## Responsibility
The temporal specialist adds before/after/during/recurring constraints to events and states. It identifies time points, intervals, and orderings, then attaches TemporalFacets to relevant objects and emits temporal ordering constraints.
---
## Manifest
```
declared_facet_kinds:
- temporal
declared_constraint_types:
- before
- after
- during
- overlaps
- starts_at
- ends_at
- recurring
budget_caps:
max_objects_proposed: 4
max_facets_emitted: 8
max_constraints_emitted: 12
max_merges: 0
max_splits: 0
```
---
## Output: TemporalFacet
Attach to any object of kind `event`, `state`, or `procedure_step`.
```
TemporalFacet fields used:
- time_point: absolute or relative (if known)
- interval_start: if event has a duration
- interval_end: if event has a duration
- temporal_order_refs: constraint_ids expressing ordering
- is_recurring: true/false
- recurrence_pattern: if recurring
```
---
## Output: Constraints
### `before(A, B)`
Event or state A occurs before event or state B.
Emit when:
- Explicit connective: `before`, `prior to`, `earlier`, `first ... then`
- Narrative sequence: events described in causal or sequential order
- Completed event followed by a subsequent event
Strength: `hard` for explicit connectives, `soft` for narrative inference.
### `after(A, B)`
Event A occurs after event B. Equivalent to `before(B, A)` but retains the original linguistic direction.
### `during(A, B)`
Event A occurs within the span of event B.
### `overlaps(A, B)`
Events A and B share some time without one being fully within the other.
### `starts_at(E, time_expression)`
Argument_refs: [event_id, time_object_id]. Emit when a specific start time is mentioned.
### `ends_at(E, time_expression)`
Argument_refs: [event_id, time_object_id]. Emit when a specific end time is mentioned.
### `recurring(E, pattern)`
Emit when an event is described as habitual or recurring. Argument_refs: [event_id, pattern_string_as_object].
---
## Ordering baseline algorithm
```
events = [objects in target_refs where kind = event | state | procedure_step]
for each pair (A, B) in events × events where A ≠ B:
check for explicit temporal connective between A and B
if found:
emit corresponding constraint with strength = hard
else:
check narrative order (A mentioned before B in text):
if A is a precondition or cause of B:
emit before(A, B) with strength = soft
```
---
## Contract test requirements
- Given `Alice had 5 apples. She gave Bob 2 apples.`, must emit `before(EV_initial_possession, EV_transfer)` or equivalent
- Given explicit `before X, Y happened`, must emit `before(Y, X)` with `strength: hard`
- Must not emit entity, quantity, or ownership constraints
- All temporal facets must reference at least one constraint
- All emitted constraints must have `source_module: temporal`