aiself
v0.3.0
Published
App that perceives and rewrites itself — state/structure/source-anchor are one declaration, browser side-effects collapse into one Log. Zero-model, deterministic self-representation.
Readme
aiself
An app that perceives and rewrites itself. Self-observation code trends toward zero.
A conventional runtime is a black box. To let an AI (or any tool) see "what view am I in, what can it become, where is that declared," you bolt probes onto the running app after the fact — walk the component tree, decode source maps, hand-mount globals, tee every side-effect channel into its own buffer. Thousands of lines of reverse-engineering, all because the app never declared itself.
aiself takes the other route: the app's state, structure, and source anchor are one declaration, so perceiving itself is a zero-cost projection (a read), not collection (a dig). The runtime value is just the projection of what the source already says.
Core proposition
An app's state, structure, and source location are the same declaration. "Perceiving itself" is a free projection of that declaration — not after-the-fact probing.
This splits cleanly into two halves, and aiself is exactly those two halves:
1. self-representation — grow the structure, project the value
The structure (which fields exist, their value domains, where they're declared) is statically extractable from source via a zero-model AST walk. The value (which view it's in right now) is a runtime projection. Join them and you have a complete Self — with no hand-written projection logic.
import { discover, screen, synth } from 'aiself'
// Grow schema from real store source (fields + value-domains + source anchors), zero model:
const schema = await discover('src/store')
// → [{ store: 'appUIStore', anchor: 'src/store/appUI.ts:3',
// fields: [{ name: 'mode', domain: ['chat','im','workbench'], line: 17 }, ...] }, ...]
// Join schema ⋉ runtime snapshot → a complete Self. `view` is the field with a
// finite value-domain — identified structurally, never hand-picked:
const self = synth(schema[0], { mode: 'im', showSideBar: true })
// → { view: 'im', state: {...}, anchor: 'src/store/appUI.ts:3', ... }
screen(self) // projection of the current self — no walk, no patch, no source-map decode2. self-log — five side-effect channels collapse into one Log
Browser side-effects (console, fetch, XHR, SSE, uncaught errors) are runtime events — they live in no declaration, so they can't be deleted. But their shape can be unified: instead of five scattered monkey-patches each pushing to its own ring buffer, all five converge into one append-only Log.
import { installSelfLog } from 'aiself'
const sink = { seq: [], now: () => performance.now() }
const restore = installSelfLog(window, sink)
// every console / fetch / XHR / error / SSE event → one ordered sink.seq, one shape
restore() // un-patch (test isolation / hot reload)Iron laws
- Zero model. The perception half is fully deterministic — declaration plus projection, no LLM. A model only enters the self-rewrite half (editing code), decoupled from perception. This is the spine.
- No regex on syntax. Object literals and type annotations are syntactic; they're parsed with a real AST (tree-sitter wasm), never pattern-matched.
- Fixture-first. The proposition is a pure function (declaration → projection); red fixtures pin the contract against real source, not a greenhouse.
API
| Export | Side | What it does |
|---|---|---|
| grow(code, file) | Node | AST-walk one source string → store schemas (Grown[]) |
| discover(dir) | Node | Scan a directory of stores → all schemas |
| synth(schema, snapshot) | pure | Join schema ⋉ runtime values → a Self |
| screen(self) | pure | Project the current self |
| installSelfLog(win, sink) | browser | Patch five channels into one Log; returns restore |
| log(seq, entry) | pure | One append |
Status
Experimental seed. Proposition is pinned by fixtures against this repo's real stores.
