diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..56bc796 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Report a defect with a clear repro +title: "[bug] " +labels: bug +assignees: '' +--- + +## What happened + +A clear description of the behavior you observed. + +## What you expected + +A clear description of the behavior you expected. + +## Minimal reproduction + +```python +# Smallest possible snippet that reproduces the issue. +# Include any non-default config values. +``` + +## Environment + +- Temporal Gradient version: (e.g. 0.3.0) +- Python version: (output of `python --version`) +- OS: +- PyYAML installed: yes / no + +## Additional context + +Stack traces, telemetry packets, or notes about what you tried. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..3aec799 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,32 @@ +--- +name: Feature request +about: Propose a new capability — lead with the use case +title: "[feature] " +labels: enhancement +assignees: '' +--- + +## The use case + +What are you trying to do? Describe the situation, not the proposed +solution. "I need to do X and the current API forces Y" is more useful +than "please add method Z." + +## Why the current API doesn't fit + +What did you try? Where did it break down? + +## A sketch of what might work + +Optional. If you have a concrete API in mind, show it. If not, leave +this blank — the maintainer can propose a shape that fits. + +```python +# Example usage (rough is fine) +``` + +## Scope check + +- Does this fit within the project's stated scope? See [CONTRIBUTING.md](../../CONTRIBUTING.md#whats-in-scope). +- Does it require a new runtime dependency? (The core package is zero-dependency by design.) +- Does it change the telemetry packet schema? diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..b9ac7d3 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,29 @@ +## What this PR does + +A short description of the change. + +## Why + +The motivation. If this fixes a bug, link the issue. If it adds a +capability, describe the use case. + +## Type of change + +- [ ] Bug fix +- [ ] New feature (non-breaking) +- [ ] Breaking change (telemetry schema, public API, or default behavior) +- [ ] Documentation only +- [ ] Refactor / internal cleanup + +## Checklist + +- [ ] `pytest` passes locally on Python 3.10+ +- [ ] New behavior is covered by tests +- [ ] Public API changes are reflected in [`docs/architecture.md`](../docs/architecture.md) +- [ ] [`CHANGELOG.md`](../CHANGELOG.md) updated under `[Unreleased]` if user-visible +- [ ] No new runtime dependencies added to the core package (or discussed first) + +## Notes for the reviewer + +Anything non-obvious: trade-offs, things you considered and rejected, +follow-ups that should be separate PRs. diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..17f9d66 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,45 @@ +name: publish + +on: + push: + tags: + - 'v*.*.*' + +jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install build tooling + run: python -m pip install --upgrade pip build + - name: Build sdist and wheel + run: python -m build + - name: Upload dist artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + publish-pypi: + name: Publish to PyPI + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/project/temporal-gradient/ + permissions: + id-token: write # trusted publishing (OIDC) — no API token needed + steps: + - name: Download dist artifacts + uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 607fd1d..e098e96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Changelog +## v0.4.0 — Launch-ready presentation + +**Release focus:** make the project legible to first-time visitors — +no code changes to the engine, but a materially different surface for +anyone landing on the repo. + +### Added + +- `examples/showcase.py` — deterministic 30-second demo. Same 20-event + stream fed to a naive LRU and to Temporal Gradient. The naive system + evicts the critical signal with routine traffic; Temporal Gradient + retains it as the sole survivor. +- `CONTRIBUTING.md` — scope/out-of-scope, dev setup, PR expectations. +- `CODE_OF_CONDUCT.md` — Contributor Covenant 2.1. +- `.github/ISSUE_TEMPLATE/bug_report.md`, + `.github/ISSUE_TEMPLATE/feature_request.md`, + `.github/PULL_REQUEST_TEMPLATE.md`. + +### Changed + +- README rewritten to lead with a tagline ("An engine that gives + software a sense of its own time"), a motivation paragraph, and the + showcase output above the fold. Adds CI / license / Python / status + badges, the ASCII data-flow diagram inline, a sample telemetry packet, + a "What this is not" callout, and a comparison table vs. rate + limiters, LRU, and vector databases. + +### No engine changes + +The clock, salience, memory, policies, and telemetry modules are +unchanged from v0.3.0. All 166 tests pass. No migration required. + ## v0.3.0 — Cleanup & simplification **Release focus:** remove accumulated compatibility cruft, consolidate diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..c6134ff --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,52 @@ +# Code of Conduct + +## Our pledge + +We pledge to make participation in this project a harassment-free +experience for everyone, regardless of age, body size, visible or +invisible disability, ethnicity, sex characteristics, gender identity +and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +## Our standards + +Examples of behavior that contributes to a positive environment: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by mistakes, + and learning from the experience +- Focusing on what is best for the overall community + +Examples of unacceptable behavior: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political + attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may +be reported by opening a private issue, or contacting the project +maintainer directly via the email listed on the GitHub profile of the +repository owner. + +All complaints will be reviewed and investigated promptly and fairly. +The maintainer is obligated to respect the privacy and security of the +reporter. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c0a37c0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,70 @@ +# Contributing to Temporal Gradient + +Thanks for your interest. This project is small, opinionated, and built +around a specific mathematical model. Contributions are welcome — please +read this short guide before opening a PR. + +## What's in scope + +- New salience scorers (novelty or value implementations behind the + existing interfaces in [`temporal_gradient/contracts/`](temporal_gradient/contracts/)) +- Adapters for real event sources (queue consumers, log tailers, webhook + receivers) — likely as separate examples rather than core +- Persistence backends for the memory store +- Performance work, additional test coverage, documentation improvements +- Bug fixes (with a failing test that demonstrates the bug) + +## What's out of scope + +- Claims about cognition, consciousness, or subjective time experience + — see [`docs/safety.md`](docs/safety.md). The framework is dynamics, + not theory of mind. +- Renaming or restructuring core state variables (Ψ, τ, S) — these are + intentionally fixed and load-bearing across docs. +- Breaking changes to the telemetry packet schema without a SCHEMA_VERSION + bump and a migration note. + +## Before you open a PR + +1. **Run the tests.** `pytest` should pass on Python 3.10, 3.11, and 3.12. +2. **Add a test** for any behavioral change. The test suite is the + contract — see [`tests/`](tests/) for style. +3. **Keep the diff focused.** One concern per PR. Refactors separate from + features separate from fixes. +4. **Update docs** if you change a public interface or default. The + architecture diagram and packet schema in [`docs/architecture.md`](docs/architecture.md) + must stay in sync with the code. + +## Development setup + +```bash +git clone https://github.com/WhatsYourWhy/The-Temporal-Gradient +cd The-Temporal-Gradient +pip install -e ".[dev]" +pytest +``` + +## Filing an issue + +Use the provided templates. For bugs, include: +- Python version +- Minimal reproduction +- Expected vs. actual behavior + +For features, lead with the use case, not the implementation. "I need +to do X and the current API forces Y" is more useful than "please add +method Z." + +## Style + +- No new runtime dependencies without discussion. The core package is + zero-dependency by design. +- Standard `black`-compatible formatting. No enforced linter, but match + the surrounding code. +- Type hints on new public functions. +- Comments only when the *why* is non-obvious. Don't narrate the *what*. + +## License + +By contributing, you agree your contributions are licensed under the +project's [MIT license](LICENSE). diff --git a/README.md b/README.md index 7cbca26..2767c8e 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,60 @@ # Temporal Gradient -A simulation framework for **salience-modulated internal time** and -**entropic memory decay**. +**An engine that gives software a sense of its own time.** -Given a stream of text events, Temporal Gradient computes: +Salience-modulated internal time and entropic memory decay for adaptive Python systems. -- **Ψ (salience)** — novelty × value, scored per event. -- **τ (internal time)** — wall time reparameterized by Ψ. High-salience - events slow the internal clock; low-salience events speed it up. -- **S (memory strength)** — per-item exponential decay over τ, with - bounded reconsolidation on access. +[![pytest](https://github.com/WhatsYourWhy/The-Temporal-Gradient/actions/workflows/pytest.yml/badge.svg)](https://github.com/WhatsYourWhy/The-Temporal-Gradient/actions/workflows/pytest.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](pyproject.toml) +[![Status: Beta](https://img.shields.io/badge/status-beta-orange.svg)](CHANGELOG.md) -Each event produces a validated telemetry packet suitable for offline -analysis, replay, or downstream policy gating. +--- -> This is a dynamics framework. It does not model cognition, -> consciousness, or subjective experience. All claims are limited to -> the state variables, dynamics, and invariants defined in code. +## Why this exists + +Every running program has a clock, but almost none of them have a *tempo*. +They tick at whatever rate the CPU allows, and every tick weighs the same. +A flood of low-stakes events and a single critical signal pass through the +same pipe at the same speed, occupying the same slice of attention. This +is fine for most software. It is conspicuously wrong for any software that +is supposed to *think*. + +Temporal Gradient unifies two questions that production agent systems +usually answer with two unrelated layers — *when should the system do +expensive work?* and *what should it remember?* — under a single signal: +**salience**. High-salience events slow the internal clock and reinforce +memory. Quiet stretches accelerate the clock and let stale items decay. +One model. One knob. Adaptive tempo. + +## See it in 30 seconds + +```bash +pip install -e ".[dev]" +python examples/showcase.py +``` + +A noisy 20-event stream with one critical signal buried at index 6: + +``` +NAIVE LRU (capacity=5) — retained: + request handled ok + cache hit ratio nominal + request handled ok + disk usage at 44 percent + request handled ok + + critical signal retained: False + +TEMPORAL GRADIENT (salience-decayed) — retained: + * [S=0.20] CRITICAL auth service unreachable must page oncall + + critical signal retained: True +``` + +A flat LRU evicts the signal with routine traffic. Temporal Gradient +retains it because salience drove the encoding strength and the routine +repeats decayed along internal time. ## Install @@ -26,15 +64,47 @@ cd The-Temporal-Gradient pip install -e ".[dev]" ``` -Python 3.10+ is required. `PyYAML` is optional — a minimal fallback -parser is used when it's absent. +Python 3.10+. `PyYAML` is optional — a minimal fallback parser is used +when it's absent. + +## How it works + +``` + text input + │ + ┌──────────────────▼──────────────────┐ + │ SaliencePipeline │ + │ H = novelty V = value │ + │ Ψ = H × V │ + └──────────────────┬──────────────────┘ + │ Ψ ∈ [0, 1] + ┌──────────────────▼───────────────────┐ + │ ClockRateModulator │ + │ dτ/dt = clamp(1/(1+α·Ψ), min, max) │ + │ τ += wall_delta × dτ/dt │ + └──────────────────┬───────────────────┘ + │ + ┌─────────────────────┼─────────────────────┐ + ▼ ▼ ▼ + DecayEngine ComputeCooldown ChronometricVector + S(τ⁺)=S·e^(−λΔτ) allow if τ ≥ T_cd → validated packet + reconsolidate (gated compute) (telemetry out) +``` + +Three state variables, governed by one input signal: + +- **Ψ (salience)** — `H × V`. Novelty × value, scored per event. +- **τ (internal time)** — `dτ/dt = clamp(1 / (1 + α·Ψ), min, max)`. High Ψ slows the clock. +- **S (memory strength)** — `dS/dτ = −λ·S`. Decay along internal time, bounded reconsolidation on access. + +See [`docs/architecture.md`](docs/architecture.md) for the full data flow, +layer responsibilities, and packet schema. ## Quickstart ```python import temporal_gradient as tg from temporal_gradient.policies.compute_cooldown import ComputeCooldownPolicy -from temporal_gradient.telemetry.schema import validate_packet_schema config = tg.load_config("tg.yaml") @@ -62,30 +132,62 @@ packet = tg.telemetry.ChronometricVector( memory_strength=0.0, ).to_packet() -validate_packet_schema(packet) - if cooldown.allows_compute(elapsed_tau=clock.tau): ... # downstream work ``` -## Core equations - -``` -dτ/dt = clamp( 1 / (1 + α·Ψ), min_rate, max_rate ) -dS/dτ = −λ·S -S(τ⁺) = min(S_max, S(τ⁻) + Δ) # reconsolidation on access +### Example telemetry packet + +Every evaluation cycle emits a validated packet for offline analysis, +replay, or downstream policy gating: + +```json +{ + "SCHEMA_VERSION": "1.0", + "WALL_T": 1.0, + "TAU": 0.15, + "SALIENCE": 0.9, + "CLOCK_RATE": 0.15, + "MEMORY_S": 0.8, + "DEPTH": 0, + "H": 0.9, + "V": 1.0 +} ``` -See [`docs/architecture.md`](docs/architecture.md) for the data-flow -diagram, layer responsibilities, and telemetry schema. +## What this is not + +- **Not a cognitive model.** The dynamics make no claim about how minds + work, consciousness, or subjective experience. All claims are limited + to the state variables and equations defined in the code. +- **Not a product.** No integration with any real event source, no + persistence layer for the memory store, no deployed callers. The + default salience scorers (rolling Jaccard for novelty, keyword counts + for value) are deliberate placeholders. Real use requires a domain- + appropriate scorer — typically embedding-based novelty. +- **Not a replacement for vector databases.** Temporal Gradient + complements semantic recall; it doesn't replace it. Pair the two. + +## How it compares + +| Approach | Importance signal | Memory model | Tempo | +|---|---|---|---| +| Rate limiter | none | none | flat | +| LRU / bounded queue | recency | recency-only eviction | flat | +| Vector DB (mem0, Letta, Zep) | semantic similarity | similarity-ranked recall | flat | +| **Temporal Gradient** | **salience (novelty × value)** | **strength decays along τ; salience reinforces** | **adaptive — τ dilates under load** | + +Temporal Gradient is positioned *upstream* of recall — it shapes what +gets encoded and how long it survives, before any similarity search runs. ## Examples ```bash +python examples/showcase.py # the 30-second case (start here) python examples/anomaly_detection.py # deterministic anomaly-stream PoC python examples/simulation.py # end-to-end simulation python examples/embedding_novelty_replay_demo.py -python scripts/chronos_demo.py # minimal clock demo +python scripts/chronos_demo.py # minimal clock-only demo ``` ## Tests @@ -94,6 +196,8 @@ python scripts/chronos_demo.py # minimal clock demo pytest ``` +CI runs the full suite on Python 3.10, 3.11, and 3.12. + ## Docs - [`docs/architecture.md`](docs/architecture.md) — layers, data flow, packet schema @@ -101,6 +205,7 @@ pytest - [`docs/glossary.md`](docs/glossary.md) — terminology - [`docs/safety.md`](docs/safety.md) — scope and safety constraints - [`CHANGELOG.md`](CHANGELOG.md) — release history +- [`CONTRIBUTING.md`](CONTRIBUTING.md) — how to contribute ## License diff --git a/examples/showcase.py b/examples/showcase.py new file mode 100644 index 0000000..8d26ae5 --- /dev/null +++ b/examples/showcase.py @@ -0,0 +1,120 @@ +"""Showcase: salience-weighted memory vs. a naive LRU on a noisy event stream. + +Deterministic, no sleeps. Both systems see the same 20 events. The naive +system keeps the last N items. Temporal Gradient retains items by +salience-decayed strength. + +The point: the critical event arrives at index 6, then 13 routine events +follow. A flat LRU evicts it. Temporal Gradient does not. + +Run: + python examples/showcase.py +""" + +import sys +from collections import deque +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from temporal_gradient.clock.chronos import ClockRateModulator +from temporal_gradient.memory.decay import ( + DecayEngine, + EntropicMemory, + initial_strength_from_psi, + should_encode, +) +from temporal_gradient.salience.pipeline import ( + KeywordImperativeValue, + RollingJaccardNovelty, + SaliencePipeline, +) + +LRU_CAPACITY = 5 +WALL_DELTA = 1.0 + +EVENTS = [ + "request handled ok", + "disk usage at 42 percent", + "request handled ok", + "cache hit ratio nominal", + "request handled ok", + "disk usage at 43 percent", + "CRITICAL auth service unreachable must page oncall", # <-- the signal + "request handled ok", + "cache hit ratio nominal", + "request handled ok", + "disk usage at 43 percent", + "request handled ok", + "cache hit ratio nominal", + "request handled ok", + "disk usage at 44 percent", + "request handled ok", + "cache hit ratio nominal", + "request handled ok", + "disk usage at 44 percent", + "request handled ok", +] + + +def run_naive(): + """Flat LRU: always keep the last N events. No notion of importance.""" + lru: deque[str] = deque(maxlen=LRU_CAPACITY) + for text in EVENTS: + lru.append(text) + return list(lru) + + +def run_temporal_gradient(): + """Salience-modulated clock + entropic decay.""" + clock = ClockRateModulator(base_dilation_factor=4.0, min_clock_rate=0.1) + decay = DecayEngine(half_life=8.0, prune_threshold=0.15) + salience = SaliencePipeline( + RollingJaccardNovelty(window_size=5), + KeywordImperativeValue(), + ) + + for text in EVENTS: + s = salience.evaluate(text) + clock.tick(s.psi, wall_delta=WALL_DELTA) + if should_encode(s.psi, threshold=0.25): + strength = initial_strength_from_psi(s.psi, S_max=1.2) + decay.add_memory(EntropicMemory(text, initial_weight=strength), clock.tau) + + survivors, _ = decay.entropy_sweep(clock.tau) + survivors.sort(key=lambda pair: pair[1], reverse=True) + return survivors + + +def critical_survived(items) -> bool: + return any("CRITICAL" in (s if isinstance(s, str) else s[0].content) for s in items) + + +def main(): + print("=" * 72) + print(" Temporal Gradient showcase: noisy stream, one critical signal") + print("=" * 72) + print(f" Stream length: {len(EVENTS)} events. Critical signal at index 6.\n") + + naive = run_naive() + print(f"NAIVE LRU (capacity={LRU_CAPACITY}) — retained:") + for text in naive: + marker = " *" if "CRITICAL" in text else " " + print(f"{marker} {text}") + print(f"\n critical signal retained: {critical_survived(naive)}\n") + + tg = run_temporal_gradient() + print(f"TEMPORAL GRADIENT (salience-decayed) — retained ({len(tg)} items):") + for mem, strength in tg: + marker = " *" if "CRITICAL" in mem.content else " " + print(f"{marker} [S={strength:.2f}] {mem.content}") + print(f"\n critical signal retained: {critical_survived(tg)}") + print("=" * 72) + print(" The naive system evicts the critical event with routine traffic.") + print(" Temporal Gradient retains it because salience drove encoding") + print(" strength, and routine repeats decay along internal time.") + print("=" * 72) + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index 31f2d9e..58dcffb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "temporal-gradient" -version = "0.3.0" +version = "0.4.0" description = "Simulation framework for salience-modulated internal time and entropic memory decay." readme = "README.md" license = { file = "LICENSE" } diff --git a/temporal_gradient/__init__.py b/temporal_gradient/__init__.py index 4d2d891..9a52cf1 100644 --- a/temporal_gradient/__init__.py +++ b/temporal_gradient/__init__.py @@ -3,6 +3,6 @@ from . import clock, memory, policies, salience, telemetry from .config_loader import load_config -__version__ = "0.3.0" +__version__ = "0.4.0" __all__ = ["clock", "memory", "policies", "salience", "telemetry", "load_config", "__version__"] diff --git a/tests/test_package_api.py b/tests/test_package_api.py index 4078f44..8a4af76 100644 --- a/tests/test_package_api.py +++ b/tests/test_package_api.py @@ -1,3 +1,5 @@ +from importlib.metadata import version as _installed_version + import temporal_gradient as tg import temporal_gradient.contracts as c @@ -7,8 +9,9 @@ def test_top_level_exports_are_present(): assert hasattr(tg, attr) -def test_package_version_is_0_3_0(): - assert tg.__version__ == "0.3.0" +def test_package_version_matches_pyproject(): + # Guards against pyproject.toml and __init__.py drifting apart on a release. + assert tg.__version__ == _installed_version("temporal-gradient") def test_contracts_all_contains_protocols():