Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
# TraceOS

> ## Software behavior, accountable to intent.

A claim about what a system does is **accountable** when it can be traced back to the
decision that asked for it, and forward to an outcome capable of contradicting it.
Nothing today does both: code annotations trace backward, monitoring traces forward, and
neither can tell you that a behavior quietly stopped matching what it was for.

**Where that stands, honestly:**

| Layer | | Status |
|---|---|---|
| **Behavior** | the Flow graph, evidence, impact, erosion detection | **works today** — v0.1, modelled against itself in CI |
| **Outcome** | an outcome carrying runtime evidence, able to refute a claim | **not built** — the layer that would detect a *wrong* claim rather than a stale one |
| **Intent** | a behavior citing the frozen decision that asked for it | **not built** — verified by provenance, never by truth |

The direction, the four horizons and the measurement that would kill each of them are in
[`docs/DIRECTION.md`](docs/DIRECTION.md). Nothing on that page is claimed as shipped.

---

> **Trace before change. Reconcile after change.**

A semantic layer that lets a coding agent see a system as behavior rather than as
files — so it can work out what a change might affect *before* making it, and check
whether the model is still true *after*.
That is the part that runs. A semantic layer that lets a coding agent see a system as
behavior rather than as files — so it can work out what a change might affect *before*
making it, and check whether the model is still true *after*.

A coding agent goes `search → modify → test`. It can change `PaymentService` without
knowing that payment participates in purchase, refund, subscription, notification and
Expand Down
139 changes: 139 additions & 0 deletions docs/DIRECTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Direction

`v0.1` answered a narrow question: does a model of a system's behavior stay true as the
code moves? It answers that now, mechanically, and the measurements are in
[`DEVELOPMENT-PLAN.md`](DEVELOPMENT-PLAN.md) and the issue tracker.

It is the wrong question to stop on. A model that stays true to the code is still only
accountable to the code.

## North star

> **TraceOS makes software behavior accountable to intent.**

"Accountable" is doing real work in that sentence and has to be defined before it can be
promised:

> A claim about behavior is **accountable** when it can be traced back to the decision
> that asked for it, and forward to an outcome that is capable of contradicting it.

Both directions are load-bearing. Trace back and nothing else, and the model is a
justification engine — every claim gets a story, none gets tested. Trace forward and
nothing else, and it is monitoring with extra steps. The pair is what neither side has.

```
Intent a frozen decision: why this behavior was asked for
↕
Behavior the Flow graph: what the system is claimed to do
↕
Outcome what actually happened, and whether it matches
```

Change can enter at any of the three. Reconciliation is how the three are brought back
into agreement — or how the disagreement is recorded when they cannot be.

## Why this direction and not "more model"

The sharpest finding of the review that produced #39–#45 was not a bug. It is that
**`claim` is the only place meaning lives in this system, and it is the field no
mechanism reads** (#45). Confidence is derived, correctly, from artifact staleness and
from an authored `supports` bit. So `confirmed` means *the addresses still resolve and
somebody said yes*. It has never meant *this sentence is true*.

That is a floor, and a peer system built on the opposite principle — annotations living
inside the code, symbol-granular for free — turned out to share it exactly. Both detect
staleness. Neither detects wrongness. The case that proved it was a claim reading *"a
dropped issue never gets a ship mark"* which was false in production; what caught it was
an incident and a person reading a diff.

**Outcome is the only layer where reality can contradict the model.** Intent and Behavior
are both statements, and two statements can only disagree about words. That is the whole
argument for this direction: it is the first shape of TraceOS in which wrongness is
addressable at all.

## Horizons

Each names what it claims, and what result would kill it. A horizon with no kill
condition is a roadmap item, not a direction.

### H1 — Erosion, gated (largely built)

What the model declared does not quietly stop resolving. Symbol-granular decay via
`git log -L` (§6.3.1), locator rot, `REFERENCE_NEVER_OBSERVED`, narrowing that reports
what it excluded (INV-025).

*Claims:* the model does not rot silently.
*Kills it:* erosion fires often enough on a healthy repository to be tuned out. Unmeasured
— #10 is the number, and no horizon past this one is worth starting before it exists.

### H2 — Outcome carries evidence

Outcome today is `declared` and nothing else (INV-015): pure AUTHORED. It gains evidence
the way an Assertion has evidence, including a `runtime` kind whose observation is the
result of a query against a real system rather than a person's judgement.

This is the first time `refutes` can be produced by a machine, and therefore the first
crack in #42.

*Requires:* splitting INV-018. "Is the running system healthy" stays out of scope
forever; "did the declared outcome occur" comes in. These are different questions and
the invariant currently conflates them.
*Claims:* a wrong claim about behavior can be detected, not merely a stale one.
*Kills it:* take three incidents that actually happened in a real repository and ask
whether this mechanism would have caught any before they became incidents. **0 of 3 and
the direction is vocabulary, not capability.**

### H3 — Intent as provenance, never as truth

An Intent entity whose evidence is a **frozen record** (§6.2.1) — an ADR, a closed issue,
a commit message, a decision minute. Verification is provenance: *is this the record that
asked for it, and has that record been superseded* — never *is this really the intent*,
which nothing can answer.

Supersession reuses the identity ledger (INV-014) unchanged: a business decision replaced
by another is exactly a `supersedes` with a reason.

*Claims:* every load-bearing behavior can name the decision that asked for it.
*Kills it:* in practice, teams cannot cite a frozen record for most behavior, because
most decisions were never written down. Then Intent is a field that stays empty and the
layer is decorative.

### H4 — The loop crosses layers

A change entering at any layer produces an impact map that reaches the other two:
a decision changes → which flows → which outcomes must be re-verified. Reconciliation
compares **expected / implemented / observed** rather than model against code.

*Claims:* the graph answers a question neither code annotations nor monitoring can.
*Kills it:* #5 — impact tiers have never been measured on a dense graph, and the reference
model suggests four tiers collapse into one. If they collapse, the cross-layer map is a
list of everything, which is the same as no map.

## What this direction refuses

Named here because a direction is defined as much by what it declines when the pressure
comes:

- **Becoming a map of the code.** Already in §16. `init` reads no source file for this
reason, and progressive discovery is not a performance decision.
- **Gating coverage.** #43: a scope wide enough to assert coverage fires at 92%; a scope
narrow enough to pass asserts nothing. Erosion gates; coverage reports.
- **Claiming a sentence is true.** #45. `confirmed` will be described as address freshness
plus an observer's judgement everywhere it appears, including the `explore` legend.
- **Six parallel change domains.** Requirement, business, architecture, code, config and
operational changes are all real, but four of them have no diff, no locator and no git.
They enter as triggers citing a record, never as surfaces to scan. The gate already
fails 92% on the one surface that *does* have artifacts.
- **A new entity because the vocabulary would be tidier.** The admission rule stands:
nothing enters unless something fails without it.

## The number that governs all of it

**#10 — nobody has measured what it costs to keep a model current.** A peer's trial gave
a fragment: four `validate` rounds, five assertions, two files of fifty-six. Nobody has
measured the weekly cost of keeping that from rotting.

If that number is small, every horizon above is worth building. If it is large, none of
them matter, because the model will be abandoned before it is ever contradicted by an
outcome. It is the cheapest measurement on this page and the only one that decides
whether the others get made.
84 changes: 84 additions & 0 deletions docs/decisions/015-intent-behavior-outcome.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# ADR-015 — Intent ↔ Behavior ↔ Outcome

**Status:** PROPOSED
**Supersedes:** nothing yet. `v0.1`'s framing ("preserve system understanding across
change") stays in force until the kill condition below is run.

## Context

`v0.1` models behavior and verifies that the model has not rotted against the code. The
review that produced #39–#45 established that this is a smaller claim than the project
had been making, for a reason that is structural rather than fixable:

`claim` is the only field carrying meaning, and no mechanism reads it (#45). Confidence
derives from artifact staleness plus an authored `supports` bit, so `confirmed` means
*the addresses resolve and somebody said yes*. A peer system built on the opposite
principle — annotations inside the code, symbol-granular without any of this machinery —
shares the floor exactly. Both detect staleness; neither detects wrongness.

Behavior verified only against code is accountable only to code.

## Decision

Re-frame the system around three layers rather than one:

```
Intent ↕ Behavior ↕ Outcome
```

- **Intent** — why a behavior was asked for. Evidence is a **frozen record** (§6.2.1):
an ADR, a closed issue, a commit message, a decision minute. Verified by **provenance,
never by truth** — *is this the record that asked for it, has it been superseded* —
because *is this really the intent* is not answerable by any mechanism. Supersession
reuses INV-014 unchanged.
- **Behavior** — the existing Flow graph. Unchanged.
- **Outcome** — gains evidence the way an Assertion has it, including a `runtime` kind
whose observation is a query result rather than a judgement. This is the only layer at
which reality can contradict the model, and therefore the only route to detecting a
wrong claim rather than a stale one.

Change may enter at any layer. Reconciliation compares **expected / implemented /
observed**.

### What the framing does not change

The skill set stays at three. `traceos init` stays a bootstrap operation, the validator
stays an engine capability, implementation stays the agent's own work. Four routes into
the model stay four: Declared / Observed / Inferred / Unknown — `Implemented` is an
evidence *kind*, not a route, and `Contradicted` is DERIVED by the validator and must
never be an authored label (INV-001).

## Consequences

**INV-018 must be split.** It currently conflates two questions:

| Question | Status |
|---|---|
| is the running system healthy — uptime, latency, CPU, incidents | out of scope, permanently |
| did the **declared outcome** occur | in scope, and the point of H2 |

Without that split, Outcome cannot carry runtime evidence and the direction is
vocabulary only. With it, the boundary needs restating precisely enough that nobody
reads it as permission to build monitoring.

**INV-015 changes.** Outcomes are declared *and* evidenced, rather than declared only.

**Intent is the weakest layer and must be labelled as such.** It has no artifact to hash,
no symbol for `git log -L`, no locator that can rot in the way code does. Promoting an
unverifiable layer to the headline is exactly the failure #45 names, and provenance is
the only honest verification available for it.

## Kill condition

This ADR is PROPOSED, not ACCEPTED, and one measurement decides it:

> Take three incidents that actually happened in a real repository. For each, ask whether
> a model carrying Intent and an evidenced Outcome would have caught it before it became
> an incident.

**0 of 3 and this is a vocabulary change**, which is not worth a specification rewrite.
1 of 3 or better and it is the first mechanism in this project capable of contradicting a
claim, which is worth every other thing on the roadmap.

Prerequisite: #10, the cost of keeping a model current. A direction whose models are
abandoned before an outcome ever contradicts them is decided by attrition, not by this.
27 changes: 27 additions & 0 deletions docs/semantic-specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,33 @@ the per-language funcname heuristics git already ships, and falls back to the
whole-file hash when git cannot resolve the anchor — loud, never silent, because a
bound that cannot be established is not a bound that found nothing (INV-026).

#### 6.3.2 An observation must outlive the commit it was made on

Narrowing first asked `git log -L :symbol:path <observed_ref>..HEAD`, which needs the
branch's commits to still exist. **A squash merge deletes them.** Measured here: one
merge left 7 of this repository's own 25 observed refs unreachable, and the next merge
orphaned more. It reproduced only in CI, because a developer's clone keeps them alive in
the reflog and in stale branches — so the failure was invisible exactly where the model
was authored and total where it was checked.

An observation therefore records `observed_symbol`: a hash of the cited symbol's text at
the moment it was read. The line range is derived from git's own hunk header at check
time and never stored, so INV-022 is untouched; what is stored is a hash of what the
observer actually read, which is what an observation is for.

Order of answers, strongest first:

| | Needs history | Answers |
|---|---|---|
| whole-file hash unchanged | no | not changed |
| `observed_symbol` unchanged | no | symbol not changed |
| `git log -L <ref>..HEAD` | yes | whether the symbol moved in that range |
| none of the above | — | could not establish → whole-file, loud, and it says why |

`OBSERVED_REF_UNREACHABLE` reports observations made before this existed, and the
discharge is to observe again — which is a real read at a commit that still exists, not
a stamp.

**The author now owes what file granularity used to cover by accident.** A claim must
cite every symbol whose change could falsify it. Before narrowing, a claim spanning two
functions but citing one still decayed when its sibling moved; after narrowing it does
Expand Down
3 changes: 3 additions & 0 deletions examples/traceos-itself/observations/2026-09.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -314,3 +314,6 @@
{"assertion": "assert.trace.unknown-is-gated", "reference": "traceos/cli.py", "observed_at": "2026-09-11T18:16:19.318636Z", "observed_ref": "9357adb7547eb637839404088bab8937ed572ace", "observed_blob": "3cc0665262e431fa4f8edb635bbbfc4ee2b43387", "observed_norm": "23963bf5a67d5392ce14a4c65a239ec001dc427e", "supports": "supports", "observer": "agent", "kind": "implementation"}
{"assertion": "assert.reconcile.unverifiable-is-not-verified", "reference": "traceos/engine.py#Git", "observed_at": "2026-09-11T18:19:54.627968Z", "observed_ref": "4e9014f677a75821a3b8db89bf1f1aa31c5e44a4", "observed_blob": "4bf4c97834b4748b848846a1db55eb5cdd6dd4d5", "observed_norm": "c115deb68fa520612eb03056e83794c674430aa5", "supports": "supports", "observer": "agent", "kind": "implementation"}
{"assertion": "assert.trace.unknown-is-gated", "reference": "traceos/cli.py", "observed_at": "2026-09-11T18:19:54.829412Z", "observed_ref": "4e9014f677a75821a3b8db89bf1f1aa31c5e44a4", "observed_blob": "7a2c4566f24d0a8bb63250a1b6c96a2802255d33", "observed_norm": "ecde6d31c34960f1b3d99c85f7898d00e3d9a030", "supports": "supports", "observer": "agent", "kind": "implementation"}
{"assertion": "assert.reconcile.confidence-decays", "reference": "traceos/engine.py#computed_confidence", "observed_at": "2026-09-11T18:23:29.861510Z", "observed_ref": "f0b47095657860d26acf593560cc6d744c5c10e7", "observed_blob": "3f034098d829a936f5477e18640514a13487bcbc", "observed_norm": "601e1938ea6f5e7627742ce917a5e330a22689db", "supports": "supports", "observer": "agent", "kind": "implementation", "observed_symbol": "3932bff17583b96baa7e6a8bd272b6184dad9e7f"}
{"assertion": "assert.reconcile.unverifiable-is-not-verified", "reference": "traceos/engine.py#Git", "observed_at": "2026-09-11T18:23:29.993936Z", "observed_ref": "f0b47095657860d26acf593560cc6d744c5c10e7", "observed_blob": "3f034098d829a936f5477e18640514a13487bcbc", "observed_norm": "601e1938ea6f5e7627742ce917a5e330a22689db", "supports": "supports", "observer": "agent", "kind": "implementation", "observed_symbol": "d772d948cfb6e3b4d3e68b55791c37251c7915fb"}
{"assertion": "assert.understand.observes-before-asserting", "reference": "traceos/engine.py#observe", "observed_at": "2026-09-11T18:23:30.159444Z", "observed_ref": "f0b47095657860d26acf593560cc6d744c5c10e7", "observed_blob": "3f034098d829a936f5477e18640514a13487bcbc", "observed_norm": "601e1938ea6f5e7627742ce917a5e330a22689db", "supports": "supports", "observer": "agent", "kind": "implementation", "observed_symbol": "7c7b8a3be2ef800da0814a052933509c8e201e61"}
24 changes: 24 additions & 0 deletions tests/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,30 @@ def run(*a):
[n["assertion"] for n in decay["narrowed"]] == ["assert.example.works"],
str(decay["narrowed"]),
)

# Ref-independent narrowing: the observation must survive its own commit
# being rewritten, because a squash merge does exactly that. Measured on this
# repository, one squash orphaned 7 of 25 observed refs.
sym_before = git.symbol_blob("mod.py", "alpha")
run("checkout", "-q", "--orphan", "rewritten")
run("add", "-A")
run("commit", "-qm", "history rewritten, every old commit orphaned")
check(
"TOOL symbol decay",
"the old ref is now unreachable, as after a squash merge",
git.reachable(head) is False,
)
check(
"TOOL symbol decay",
"a content hash still narrows when the ref is gone",
git.changed_since(head, "mod.py", blob, None, "alpha", sym_before) is False,
)
check(
"TOOL symbol decay",
"and it still reports a real change to the cited symbol",
git.changed_since(head, "mod.py", blob, None, "alpha", "0" * 40) is True,
)

finally:
shutil.rmtree(tmp, ignore_errors=True)

Expand Down
Loading