This page mirrors the canonical CHANGELOG.md at the repo root via¶
pymdownx.snippets. Edit the root file, not this page.¶
Changelog¶
All notable changes to activegraph are documented here.
The format follows Keep a Changelog; versioning follows Semantic Versioning. Per-version migration notes reference the Migration from v0.7 cookbook, the canonical runbook for upgrading runs and code across milestones.
The doc site mirrors this file at
Changelog via the
mkdocs snippet plugin — edit CHANGELOG.md at the repo root.
[v1.2.0] — 2026-07-03¶
The GraphStore release: the materialized graph projection becomes a pluggable seam with a FalkorDB backend, contributed by @dudizimber (issues #38, #41, #43,
45; PRs #39, #46). The architectural decisions are locked in¶
CONTRACT.md § v1.2 (#1–#6), including the provenance note recording that the lock was written retroactively during release preparation.
Added¶
GraphStoreabstraction for the materialized graph projection, withInMemoryGraphStore(the default) andFalkorDBGraphStorebacking the current-state view in a FalkorDB graph.Graph(graph_store=...),Runtime.load(..., graph_store=...), andRuntime.fork(..., graph_store=...)select where the projection is materialized; the event log remains the source of truth. In FalkorDB, objects are:AGNode:AGObjectnodes and relations are nativeAGRelationedges between them, so the projection is a real, traversable graph; dangling relations are supported via bare:AGNodeplaceholder endpoints. The store connects to a running FalkorDB viaurl=/host=arguments or theFALKORDB_URL/FALKORDB_HOST(_PORT/_USERNAME/_PASSWORD) environment variables, falling back to the embeddedfalkordbliteengine. Install withpip install 'activegraph[falkordb]'(server client) or'activegraph[falkordb-embedded]'(embedded engine). See the Using the FalkorDB graph store guide.GraphStorequery hooks (find_objects,find_objects_in_types,find_relations,neighborhood,match_chain) that let backends evaluate structural queries close to the data.Graph.objects(type=...),Graph.objects_in_types(...),Graph.relations(...),Graph.get_relations(...),Graph.neighborhood(...), and the pattern matcher delegate to these hooks. The default implementations compute results in Python (identical to before), whileFalkorDBGraphStoreoverrides them with Cypher so type filters, relation lookups, neighborhood walks, and whole pattern chains run inside the database instead of scanning the whole projection.wherepredicates still evaluate in Python since the structured payload is stored as JSON.- Agent-discovery files at the doc-site root:
/llms.md, a markdown mirror of/llms.txtproduced by a post-build hook (scripts/mkdocs_hooks.py) for AI agents that probe well-known.mdroot paths on a static host that cannot do Accept-header content negotiation, and/robots.txtwith the canonicalsitemap.xmlpointer (copied verbatim fromdocs/robots.txt). Both are gated bytests/test_llms_txt.pyin the docs workflow. PyPI metadata now carriesDocumentation,Repository,Changelog, andIssuesproject URLs so the package is verifiable as the official distribution for docs.activegraph.ai.
Changed¶
- Pattern matching now resolves the entire structural chain through the
pushed-down
Graph.match_chain(...)hook instead of scanningall_objects()/all_relations()on every event. OnFalkorDBGraphStorea whole pattern (node types + relation types/directions) collapses into a single index-backed Cypher query rather than one round-trip per hop per candidate; the default store reproduces the same depth-first walk, so match results and their order are unchanged. Semantics stay homomorphic (a node or relation may fill more than one position). Node{prop: value}equality andWHEREpredicates continue to evaluate in Python over the resolved chains. - Cascade-deleting an object's relations (on
object.removed) now uses two scopedfind_relationslookups (out-edges + in-edges) instead of scanning every relation, so onFalkorDBGraphStoreit is an index-backed query rather than a full edge scan. - Matching
RelationBehaviors now filters candidate edges via the pushed-downGraph.relations(type=...)hook instead of scanning every relation on each event; onFalkorDBGraphStorethis is an index-backed lookup. - Building a behavior
Viewwith a type-scopedinclude_typesspec (and noaroundanchor) now pushes the type filter into the store viaGraph.objects_in_types(...)(a singletype IN [...]query onFalkorDBGraphStore) instead of materializing every object and filtering in Python. The default store preserves the same single-pass order, so the in-memoryViewis byte-for-byte unchanged.
Tests¶
- The test suite is now a CI gate (
.github/workflows/tests.yml, CONTRACT v1.2 #6):pytest -m "not slow"runs on every push to main and every pull request, on a Python 3.11 + 3.12 matrix. The 3.12 leg installsfalkordb-embeddedso the FalkorDB store andGraphStoreConformancetests execute, and a Postgres 16 service container runs the PostgresEventStoreconformance tests. Every non-slow test now executes on at least one matrix leg; previously no workflow ran the suite at all. GraphStoreConformance(activegraph/store/graph_conformance.py) is the pytest-collectable extension contract for graph-store backends;InMemoryGraphStoreandFalkorDBGraphStoreboth inherit it (CONTRACT v1.2 #5).
Migration notes¶
- No store schema migration is required. Runs that do not pass
graph_store=behave byte-for-byte as before;InMemoryGraphStoreremains the default. - The FalkorDB backend is opt-in via the new
activegraph[falkordb](server client) oractivegraph[falkordb-embedded](embedded engine, Python 3.12+) extras. Neither is included in[all]or[dev]. - The FalkorDB projection layout changed between the pre-release
relations-as-nodes shape (PR #39) and the released native-edge
shape (PR #46). No migration tooling exists or is needed: the
projection is disposable — point
Runtime.load(..., graph_store=...)at the event log and it rematerializes in the new layout. - Event consumers are unaffected: v1.2.0 adds no new event types, reason codes, or error classes.
[v1.1.0] — 2026-06-10¶
Added¶
Runtimenow performs bounded provider-call retries for transient LLM failures (llm.network_error,llm.rate_limited) before the terminalbehavior.failedpath. Failed attempts are visible as error-shapedllm.respondedevents and successful object provenance points at the successful retry attempt.activegraph inspect --memorenders memo objects with the same operator format used byactivegraph quickstart.activegraph inspect --search <query>searches event ids, types, actors, and payload JSON.activegraph fork --set <pack>.<setting>=<value>records fork-local pack setting overrides aspack.settings_overriddenevents. Pack loading applies and validates those overrides before post-fork execution resumes.OpenTelemetryMetricsships as a second optional metrics backend alongsidePrometheusMetrics. Install withpip install 'activegraph[opentelemetry]'.OpenAIProvidernow supports the shared LLM/tool loop by translating framework tool definitions to OpenAI Chat Completions function tools and extracting returnedtool_callsinto the sharedToolCallshape.- Release drift gates now cover CLI-reference flags, executable Python doc snippets, reason-code docs, and tagged-release version correspondence.
Changed¶
LLMCache.from_eventsignores error-shapedllm.respondedevents so retry failures cannot poison replay caches.- The trace printer annotates LLM retry attempts and error-shaped responses.
- Docs now use
patch.appliedfor direct object patches instead of the non-emittedobject.patchedevent name. - The failure model now states why strict replay
ReplayDivergenceErrorand dispatch-time contract failures escape instead of becomingbehavior.failed. - The LLM provider reference now treats OpenAI and Anthropic tool use as
supported through the same runtime contract. Native provider
structured-output modes remain deferred in
FUTURE_IDEAS.md.
Tests¶
- Pinned
Diff.is_identicalas a bool property (is True/is False) to close issue #28. - Added retry, replay-cache, CLI-selector, fork-override, OpenTelemetry, OpenAI tool-parity, doc-snippet, CLI-reference, and reason-code regression coverage.
Migration notes¶
- No store schema migration is required.
- Event consumers that treat framework event types as a closed list
should allow the new
pack.settings_overriddenevent. - The new OpenTelemetry backend is optional; existing Prometheus and
custom
Metricsimplementations continue to work unchanged.
[v1.0.5.post2] — 2026-05-20¶
Type-system concepts page. A maintainer-driven doc-gap review found
that the framework's type system is documented across four concepts
pages (graph.md, events.md, relations.md, patches.md) plus
the pack-authoring guide, with no single page answering the question
a new reader arrives with: what types are framework-defined, what
types are developer-defined, and how do they compose? In
particular, "are there framework base object types?" — the answer
is no — was reachable only by assembling fragments from three pages.
v1.0.5.post2 ships one new concepts page. No framework code changes. No new public API. No reshape of any locked decision below v1.0.5.post2.
The single finding¶
v1.0.5.post2 #1 — A type-system concepts page lands at
docs/concepts/type-system.md, slotted into
the Concepts nav between Graph and Events. The
page commits to four claims: (a) object types
are developer-defined strings; the framework
ships zero base object types; (b) relation
types follow the same model; (c) event types
are framework-defined, with the complete
enumerated set in named families
(lifecycle / graph mutations / behavior dispatch
/ patterns / LLM / tools / patches / approvals
/ pack lifecycle); (d) patch lifecycle states
(proposed | applied | rejected) are
framework-defined.
Added¶
docs/concepts/type-system.md(v1.0.5.post2 #1). The new page. Sections: the framework-defined layer (event types, fully enumerated); the developer-defined layer (object types); the developer-defined layer (relation types); how the three layers compose; patch lifecycle states; designing an ontology; the Diligence pack ontology as the worked example. The page makes the framework's "no base object types" stance explicit because users from typed-schema backgrounds (databases, Pydantic, GraphQL, Protobuf) arrive expecting a schema-definition step. The deep-research-agent user-test finding ("object types should be nouns describing their role in the pipeline, not just data bags") is the surfacing path for the ontology design guidance.
Changed¶
README.md: new## The type system at a glancesection betweenConcepts at a glanceandA small example. Three short beats — event types are fixed (full enumerated list inline), object and relation types are yours (no central schema, no registration), patch states are fixed — plus a pointer to the new concepts page. The bridge between the primitive index and the example: a reader who has skimmed the twelve primitives now knows the vocabulary the example uses (object.created, the customtask.completed, thetaskanddepends_onstrings) before they hit the code. The section calls out thetask.completedcustom-event-type usage in the example explicitly so the framework-vs-application event distinction lands before the code rather than after it.mkdocs.yml: nav addsType system: concepts/type-system.mdto the Concepts section between Graph and Events; themkdocs-llmstxtplugin'ssections.Conceptslist adds the matching entry with its one-sentence description. Thetests/test_llms_txt.pygate from v1.0.5 #1 picks up the new page automatically at build time (the generated/llms.txtand/llms-full.txtregenerate frommkdocs.yml+ thedocs/source on every build, so the new page appears in both).pyproject.toml:versionbumps1.0.5.post1→1.0.5.post2.activegraph/__init__.py:__version__tracks the bump (thetest_version_syncgate asserts it matchespyproject.tomlbyte-identically).tests/snapshots/errors/{internal_bug__pattern_unknown_op, internal_bug__graph_view_unknown_op,schema_version_mismatch}.txt: rebaselined the embedded version string from1.0.5.post1to1.0.5.post2(same three snapshots v1.0.5.post1 rebaselined for the same reason — they renderactivegraph.__version__inline per the internal-bug context format and the schema-version-mismatch context format).
CONTRACT amendments¶
v1.0.5.post2milestone added with one numbered finding (v1.0.5.post2 #1) and a "deliberately does NOT touch" section. Single-finding milestone — same scope discipline as v1.0.1 / v1.0.2 / v1.0.2.post1 / v1.0.5 / v1.0.5.post1. Appended as a new section after v1.0.5.post1 per Standing Rule §1.
v1.1 backlog (filed in v1.1-plan.md)¶
object.patchedevent-name drift indocs/concepts/events.md. The page's "Object mutations" family listsobject.patched, but the code emitspatch.appliedfor the directgraph.patch_object(...)shortcut and neverobject.patched. Either correct the doc (drop the name or rename topatch.applied) or add the event in code; the fix shape depends on whether direct mutations were intended to be distinguishable from patch applies. Surfaced during the v1.0.5.post2 diagnosis; out of scope for a "no concepts-page reshape" docs-only release.- Reason-code taxonomy as a dedicated concepts page. The
behavior.failed/tool.respondedreason=field carries a closed taxonomy (llm.network_error,llm.parse_error,tool.unknown_tool,tool.max_turns_exhausted, …) documented only across the per-error pages. A dedicated reference (orfailure-model.mdexpansion) could enumerate the codes the way v1.0.5.post2 #1 enumerates the event types.
Migration from v1.0.5.post1¶
Forward-compatible. No code changes required. The runtime API,
public surface, and CI gates are unchanged. The doc site grows
by one page; existing pages stay byte-identical. /llms.txt and
/llms-full.txt regenerate automatically on the next
mkdocs build (the v1.0.5 #1 structural-drift guarantee).
- Every v1.0.5.post1 surface (LICENSE, NOTICE, CONTRIBUTING.md, issue templates, license metadata) stays byte-identical.
- Every v1.0.5 surface (
/llms.txt,/llms-full.txt,mkdocs-llmstxtplugin,tests/test_llms_txt.py) stays byte-identical apart from the new entry the plugin picks up frommkdocs.yml. - Every v1.0.4 / v1.0.3 / v1.0.2.post1 / v1.0.2 / v1.0.1 / v1.0 surface stays byte-identical.
[v1.0.5.post1] — 2026-05-19¶
Pre-launch foundation pass before the repository is flipped public.
Three coupled deliverables: the framework's license switches from
MIT to Apache 2.0; a CONTRIBUTING.md lands with an issues-first
contribution policy; the .github/ISSUE_TEMPLATE/ surface lands with
three structured templates plus a config.yml that disables blank
issues. Coupled because shipping any one without the others would
leave a public repository in a half-stated posture.
No framework code changes. No new public API. No reshape of any
locked decision below v1.0.5. The release surface is repo-root
metadata (LICENSE, NOTICE, pyproject.toml's license field,
README.md's license section), contributor-facing prose
(CONTRIBUTING.md), and the GitHub issue-template surface
(.github/ISSUE_TEMPLATE/). The framing — post-release patch
between a numbered milestone and the next — matches the v1.0.2.post1
precedent (CONTRACT v1.0.4 #6's appended archeology section is the
canonical example of how a .postN release lands in CONTRACT under
Standing Rule §1).
The finding (v1.0.5.post1 #1)¶
v1.0.5.post1 #1 — Active Graph is licensed under Apache 2.0 from
v1.0.5.post1 forward. Three reasons named in the
CONTRACT amendment: explicit patent grant (§3 of
the license, which MIT does not provide;
load-bearing for a framework whose primitives —
event-sourced reactive graph, relation behaviors,
binding-moment validation, pack format — are
themselves the contribution surface);
institutional standard for foundation-shaped
projects (ASF, CNCF, LF AI; matches enterprise
legal-review calibration); legal precision on
trademark / contribution / NOTICE boundaries
(§§6, 5, 4(d); MIT leaves these implicit). The
previous declared license was MIT, recorded in
pyproject.toml and README.md through twelve
milestones but never accompanied by a LICENSE
file at the repo root — this release is the
first to ship the canonical license text.
Added¶
LICENSE(v1.0.5.post1 #1). Canonical Apache 2.0 text fromhttps://www.apache.org/licenses/LICENSE-2.0.txtprefixed with a single-lineCopyright 2026 Yohei Nakajimaheader. The body below the header is byte-identical to the Apache Foundation's canonical plain-text version.NOTICE(v1.0.5.post1 #1). The Apache 2.0 §4(d) attribution pair. Two lines: project name (Active Graph) and copyright line (Copyright 2026 Yohei Nakajima). Downstream redistributors preserve NOTICE per §4(d).CONTRIBUTING.md(v1.0.5.post1 #1). Issues-first contribution policy for the framework's early public phase. Issues are open; code PRs are maintainer-only with an issue-first discussion gate; documentation PRs may be opened directly. Names the policy as a pre-launch posture (not a permanent stance) with the relaxation criteria stated. Includes the explicit Apache 2.0 §5 inbound-equals-outbound statement. Names three out-of-scope items mirroring the CONTRACT amendment's "deliberately does NOT touch" section: CLA / DCO decision,CODE_OF_CONDUCT.mdpaired with a contact channel, broader contributor surface..github/ISSUE_TEMPLATE/bug_report.md,feature_request.md,question.md(v1.0.5.post1 #1). Three structured templates prompting for the information that makes a triage pass deterministic — minimal reproduction (bugs), problem statement and current workaround (feature requests), what-tried and what-expected (questions). Each template heads with a one-line pointer todocs.activegraph.aiandCONTRIBUTING.md. Pre-labels each issue (bug,enhancement,question) so issue-list filters work without manual triage..github/ISSUE_TEMPLATE/config.yml(v1.0.5.post1 #1). Disablesblank_issues_enabledand adds twocontact_linkspointing at the docs site andCONTRIBUTING.md. Forces every issue through one of the three templates.tests/test_license.py(v1.0.5.post1 #1). Standing Rule §2 gate anchored on the contract boundary ("Active Graph is licensed under Apache 2.0 from v1.0.5.post1 forward"). Six assertions covering the five surfaces the claim binds: LICENSE carries the Apache canonical heading plus the §3 patent-grant section header; NOTICE carries the project name and copyright line; pyproject.toml's license field reads SPDXApache-2.0; noLicense ::classifier remains in pyproject; README's license section names Apache 2.0 and points at LICENSE; sanity check on tomllib availability.
Changed¶
pyproject.toml:[project].licenseswitches from{ text = "MIT" }to the SPDX string"Apache-2.0"per PEP- Adds
[project].license-files = ["LICENSE", "NOTICE"]declaring the carried metadata. Drops theLicense :: OSI Approved :: MIT Licenseclassifier — PEP 639 forbidsLicense ::classifiers when the SPDX form is used. Bumpsbuild-system.requiresfromsetuptools>=68tosetuptools>=77.0.3, the minimum that supports PEP 639's SPDX license metadata. README.md: the## Licensesection now reads "Active Graph is licensed under the Apache License 2.0" with pointers to LICENSE and NOTICE. The## Contributingsection now points atCONTRIBUTING.mdand names the issues-first policy as the pre-launch posture.activegraph/__init__.py:__version__tracks the bump to"1.0.5.post1"(thetest_version_syncgate asserts it matchespyproject.tomlbyte-identically).tests/snapshots/errors/{internal_bug__pattern_unknown_op, internal_bug__graph_view_unknown_op,schema_version_mismatch}.txt: rebaselined the embedded version string from1.0.5to1.0.5.post1(these three snapshots renderactivegraph.__version__inline per the internal-bug context format and the schema-version-mismatch context format).
CONTRACT amendments¶
v1.0.5.post1milestone added with one numbered finding (v1.0.5.post1 #1) and a "deliberately does NOT touch" section. Single-finding milestone — same scope discipline as v1.0.1 / v1.0.2 / v1.0.2.post1 / v1.0.5. Appended as a new section between v1.0.5 and v1.1 per Standing Rule §1 (the v1.0.2.post1-via-v1.0.4-#6 retroactive archeology section is the precedent for how a.postNrelease lands).
v1.1 backlog (filed in v1.1-plan.md)¶
- CLA / DCO decision. Apache 2.0 §5's implicit grant covers
the contract today; if contribution volume grows past
maintainer-review bandwidth or if enterprise legal desks
request the ceremony, decide between a CLA (with a signing
workflow) and a DCO (the
Signed-off-by:discipline). v1.1 work, not v1.0.5.post1. CODE_OF_CONDUCT.mdpaired with a contact channel. Contributor Covenant v2.1 is the standard text; the missing piece is the contact channel for reports. v1.1 picks both up together — shipping the document without the inbox would publish a hollow reporting commitment.- Relax the issues-first contribution policy. Today's maintainer-only-code-PRs posture is explicitly pre-launch; v1.1 owns the decision to broaden it based on observed contribution patterns during v1.0.x's public window.
Migration from v1.0.5¶
Forward-compatible. No code changes required. The runtime API, public surface, doc-site content, and CI gates are unchanged.
PyPI metadata changes for v1.0.5.post1 onward:
License-Expression: Apache-2.0 (PEP 639 SPDX form);
License-File: LICENSE, License-File: NOTICE (the carried
metadata files); the License :: OSI Approved :: MIT License
classifier is removed. Redistributors should update their
license-tracking metadata accordingly.
- Every v1.0.5 surface (
/llms.txt,/llms-full.txt,mkdocs-llmstxtplugin,tests/test_llms_txt.py) stays byte-identical. - Every v1.0.4 / v1.0.3 / v1.0.2.post1 / v1.0.2 / v1.0.1 / v1.0 surface stays byte-identical.
[v1.0.5] — 2026-05-19¶
AI-readable docs via llms.txt support. The v1.0.4 external
user-test surfaced that most evaluators of Active Graph in 2026 reach
the doc site through AI coding assistants (Claude Code, Cursor,
Replit) rather than browsers — and that mkdocs-rendered HTML wraps
content in navigation chrome that those agents spend tokens
unwrapping. The dominant convention for machine-readable docs is
llms.txt (Howard, 2024), adopted by Stripe,
Vercel, Anthropic's docs, Nuxt, and many others.
v1.0.5 ships both files at the doc-site root, generated at build
time from the existing docs/ markdown source. No abstraction
changes, no new runtime capability, no source-markdown changes. The
release is docs + build-infrastructure only.
The single finding¶
v1.0.5 #1 — /llms.txt (structured markdown index, ~96 lines) and
/llms-full.txt (concatenated full content, ~110K
tokens) at the docs.activegraph.ai site root,
generated by the mkdocs-llmstxt plugin inside
mkdocs build. Drift prevention is structural — no
hand-maintained llms.txt lives in the repository,
so both files cannot drift from the source markdown
they are generated from.
Added¶
https://docs.activegraph.ai/llms.txt(v1.0.5 #1). Structured markdown index with# Active GraphH1, blockquote summary, and H2 sections (Quickstart, Concepts, Guides, Cookbook, Reference, Optional) listing every doc page with a one-sentence description per curated entry. Sized for AI tools that support llms.txt-aware fetching.https://docs.activegraph.ai/llms-full.txt(v1.0.5 #1). Concatenated markdown of every doc page in reading order, sized for large-context-window AI ingestion (~110K tokens, under the 200K target). The "everything in one file" reference for AI tools that prefer comprehensive corpora.mkdocs-llmstxt>=0.2added topyproject.toml's[docs]extra. Same maintainer asmkdocstrings; the workflow auto-syncs because.github/workflows/docs.ymlinstalls.[docs].tests/test_llms_txt.py— Standing Rule §2 gate anchored on the v1.0.5 #1 contract claim. Six assertions: both files exist;llms.txthas the H1 + blockquote + at least 4 H2 sections;llms.txtreferences the three nav-anchor pages named in the amendment (concepts/graph, quickstart, at least one cookbook page);llms-full.txtcarries the H1 plus a distinctive marker phrase from the quickstart body. Marked@pytest.mark.slow; runs in thedocs.ymlworkflow aftermkdocs build.
Changed¶
mkdocs.yml: adds thellmstxtplugin block withmarkdown_description,full_output: llms-full.txt, and asections:map mirroring the existingnav:1:1..github/workflows/docs.yml: adds thepytest -m slow tests/test_llms_txt.pyverification step aftermkdocs build. Build cost: ~5 seconds on top of the existing ~5-second mkdocs build.README.md: short note under## Documentationpointing AI agents at/llms.txtand/llms-full.txt. Frames the audience so human readers understand why both URLs exist alongside the rendered site rather than replacing it.
CONTRACT amendments¶
- v1.0.5 milestone added with one numbered finding
(
v1.0.5 #1) and a "deliberately does NOT touch" section. Single-finding milestone — same scope discipline as v1.0.1 / v1.0.2 / v1.0.2.post1 (each release small, independently reviewable, and free of unrelated cleanup).
v1.1 backlog (filed in v1.1-plan.md)¶
- Content negotiation on the docs host. A worker / edge
function returning
text/markdownforAccept: text/markdownrequests, complementing the static/llms.txtand/llms-full.txtfiles. The static-files approach handles the index-and-bulk case; content negotiation handles the per-page case. Requires docs-host infrastructure GitHub Pages does not support natively. - Editorial doc-readability pass. Front-load page summaries (so the first paragraph stands alone as the page abstract), tighten cross-references, normalize terminology across the per-error catalog. Open-ended editorial work, orthogonal to v1.0.5's mechanical file-generation scope.
Migration from v1.0.4¶
Forward-compatible. No code changes required. The new files appear automatically at the doc-site root on the next deploy.
- Every v1.0.4 surface (
Graph.relations,Graph.get_relationsalias, the failure-model footers on the 10 per-error pages, CONTRACT review-overlay markers) stays byte-identical. - Every v1.0.3 surface (Graph.objects, Runtime.errors, BehaviorFailure, LLMMessage.tool_calls, WARNING log) stays byte-identical.
- Every v1.0.2 / v1.0.2.post1 surface (LLMProvider.default_model, recognizes_model, both-binding-moments validation) stays byte-identical.
Provider non-promises in v1.0.5¶
Inheriting v1.0.4 / v1.0.3 / v1.0.2 / v1.0.1 #5 (c). Specifically unchanged in this release:
LLMProviderProtocol stays at v1.0.2 #1's widened shape.- The closed CONTRACT v0.6 #11 reason taxonomy is unchanged.
- The
behavior.failedevent payload, the WARNING log format, and theBehaviorFailureshape stay byte-identical to v1.0.3 through v1.0.4.
[v1.0.4] — 2026-05-19¶
Pre-launch foundation cleanup absorbing six small findings from the
post-v1.0.3 contract review (see CONTRACT-review-findings.md §5).
No abstraction changes, no new runtime capability, no new CI gate.
Three documentation corrections, one additive API method, one test
addition, one CONTRACT archeology restoration.
The release also operationalizes the two Standing Rules adopted by the contract review banner: §1 (amendments append, never modify) shaped every v1.0.4 commit; §2 (tests anchor on the contract boundary, not the implementation's path) shaped the new tests for
1 and #4.¶
The six findings¶
v1.0.4 #1 — graph.relations(source=, target=, type=) canonical filter API (mirrors v1.0.3 #1's graph.objects fix) v1.0.4 #2 — per-error-page footer pointing at failure-model.md's "Observing failures in caller code" (10 pages) v1.0.4 #3 — WARNING-log vs BehaviorFailure field-name divergence documented in failure-model.md v1.0.4 #4 — boundary-anchored test for _requeue_unfired zero-subscriber carve-out (Standing Rule §2 shape) v1.0.4 #5 — review-overlay markers at v0 #11, v0 #16, v0.8 #19 for stale forward-pointer prose v1.0.4 #6 — appended ### v1.0.2.post1 section to CONTRACT under v1.0.2 #1, restoring archeology that v1.0.2.post1's in-place revision destroyed
Added¶
Graph.relations(source=None, target=None, type=None) -> list[Relation](v1.0.4 #1). Canonical filter API onGraph. Three kwargs compose by AND; no-kwargs returns every relation; the source/target decomposition replaces the asymmetricdirection="outgoing"|"incoming"|"both"axis on the alias. Eight filter combinations (each row of the table in CONTRACT v1.0.4 #1) are the contract claim; each is covered by a dedicated test intests/test_graph.py.
Implementation note: the method is a direct projection over
self._relations.values(), not a wrapper over get_relations.
The underlying loop is six lines, and routing through the alias
would obscure the per-row contract claim that the tests anchor
on. The duplication trade is small; the readability win is the
point.
tests/test_requeue_unfired.py::test_zero_subscriber_event_ids_are_absent_from_requeue_set_on_load(v1.0.4 #4). Boundary-anchored sibling to the existingqueue_depth == 0test. Asserts directly on the requeue set (rt._queue._q) rather than the implementation's symptom. Locks the v0.5 #8 carve-out at the contract boundary the amendment names.
Changed¶
Graph.get_relations(...)(v1.0.4 #1). Kept as a backward-compatible alias forGraph.relations. No deprecation warning in v1.0.4; v1.1's Theme A (Graph/View harmonization) owns the deprecation decision.
Documentation¶
docs/concepts/graph.md(v1.0.4 #1). The long-broken line-43 referencegraph.relations(source=claim_id)now resolves to the new method. Three canonical-form examples (source=,target=,type=) plus one line on theget_relationsalias.docs/concepts/failure-model.md(v1.0.4 #3). Adds one paragraph in the "Observing failures in caller code" section naming the intentional field-name divergence between the WARNING log (error_type/error_message— v0.8 #6 schema) andBehaviorFailure(exception_type/message— Python convention). Values are identical; only the names differ.docs/reference/errors/*.md(v1.0.4 #2). Adds the fixed one-line footer pointing atfailure-model.md#observing-failures-in-caller-codeto the 10 per-error pages whose error classes route throughbehavior.failed(identified by tracing each error class's raise sites through the runtime emission path, not by guessing from page titles). The 21 pages whose errors are raised at decoration / registration / setup / lookup time do NOT get the footer.
CONTRACT amendments¶
- v1.0.4 milestone added with six amendments and a "deliberately does NOT touch" section.
### v1.0.2.post1subsection appended as §(e)-equivalent under v1.0.2 #1 (v1.0.4 #6). Documents the validation-boundary correction (lazy-at-first-run → both binding moments), the_live.pyweakref.WeakSetmechanism, and citestests/test_llm_default_model.pySection (g) as the canonical Standing Rule §2 model.- Top-of-v1.0.2-#1 breadcrumb updated in place to point at the now-existing post1 section (the single in-place edit Standing Rule §1 permits in v1.0.4, explicitly authorized by the contract review).
- Three review-overlay markers added in place at v0 #11,
v0 #16, v0.8 #19 (v1.0.4 #5). Original prose preserved
verbatim; each overlay is bracketed
[review overlay 2026-05-19: …]so the layer boundary is explicit and greppable by date.
v1.1 backlog (filed in v1.1-plan.md)¶
- C-3 — Lock failure-routing for eval-time pattern failures.
Surfaced during v1.0.4 #2's audit. Most dispatch-time errors
route through
behavior.failed;ReplayDivergenceErrorandUnsupportedPatternErroreval-time raises deliberately escape. v1.1 should either keep the asymmetry and document the carve-out criterion in CONTRACT, or route every dispatch-time error uniformly. The current state is neither documented nor locked. - I-4 — Cross-link
replay-divergence-error.mdto replay/fixture documentation. The standard v1.0.4 #2 footer doesn't apply to this page because the error deliberately escapes rather than emittingbehavior.failed. A different cross-link is needed; phrasing depends on how C-3 resolves.
Migration from v1.0.3¶
Forward-compatible. No code changes required.
graph.get_relations(object_id=, type=, direction=)keeps working byte-identically; new code usesgraph.relations(source=, target=, type=).- All v1.0.3 surfaces (Graph.objects, Runtime.errors, BehaviorFailure, LLMMessage.tool_calls, WARNING log) are unchanged.
- All v1.0.2 / v1.0.2.post1 surfaces (LLMProvider.default_model, recognizes_model, both-binding-moments validation) are unchanged.
Provider non-promises in v1.0.4¶
Inheriting v1.0.3 / v1.0.2 / v1.0.1 #5 (c). Specifically unchanged in this release:
LLMProviderProtocol stays at v1.0.2 #1's widened shape; no further additions.- The closed CONTRACT v0.6 #11 reason taxonomy is unchanged.
- The
behavior.failedevent payload, the WARNING log format, and theBehaviorFailureshape stay byte-identical to v1.0.3.
[v1.0.3] — 2026-05-19¶
Comprehensive response to two user-test reports. Four findings span
the framework's user-facing API surface, the largest single release
since v1.0.1. The framing — patch release on the adoption-surface
milestone, one commit per finding for independent review — matches
v1.0.1 / v1.0.2. The two prior user-test findings carried forward
into this release (output_schema= UX and silent behavior.failed
UX) are addressed in #2 and #3.
The four findings¶
v1.0.3 #1 — graph.objects(type=...) as canonical query API v1.0.3 #2 — @llm_behavior(output_schema=) strict-validates at decoration time (dict-form filed as v1.1 candidate) v1.0.3 #3 — WARNING log + Runtime.errors property for behavior.failed v1.0.3 #4 — multi-turn tool-use messages carry full content blocks
Added¶
Graph.objects(type=..., where=...)(v1.0.3 #1). Canonical query API onGraph, mirroringView.objects(type=...)so call sites read the same inside and outside behaviors. External users who reached for the natural form previously hitAttributeError; the docs even showed the call.Graph.query(object_type=...)stays as a backward-compatible alias — no deprecation in v1.0.3.Runtime.errors -> list[BehaviorFailure](v1.0.3 #3). A read-only property projectingbehavior.failedevents from the graph's event log into structured named-tuples. Five fields per failure (behavior,event_id,reason,exception_type,message) plusfailed_event_idfor callers that want the full payload. The events stay the source of truth; the property is a view.BehaviorFailure(v1.0.3 #3). NewNamedTupleexported from the top-levelactivegraphnamespace. Distinct from Python's builtinRuntimeError— the name was chosen so it doesn't shadow.LLMMessage.tool_calls(v1.0.3 #4). Additive field (Optional[tuple[ToolCall, ...]], defaultNone) carrying the originatingToolCallobjects on assistant messages that triggered tool_use. The provider adapter reconstructs the wire-format content blocks from this field.doc_urlin the structured log schema (v1.0.3 #3). The JSON log formatter now emitsdoc_url(when present). Thebehavior.failedWARNING log carries the URL pointing at the reason's class-level documentation page so operators tailing logs can click through.
Changed¶
@llm_behavior(output_schema=)strict-validates at decoration time (v1.0.3 #2). Passing anything that isn'tNoneor a PydanticBaseModelsubclass raisesTypeErrorfrom the decorator with a structured message that names the actual type passed and inlines a copy-pasteable code example of the correct form. Previously, a JSON-schema dict raised aTypeErrorinternally and the runtime caught it as a generic exception, producing abehavior.failedevent with reasonllm.schema_violationand no diagnostic naming the cause. Dict-formoutput_schema=support is a v1.1 candidate.Runtime._emit_behavior_failedemits a WARNING log line (v1.0.3 #3). Everybehavior.failedemission produces exactly one log line atWARNINGlevel on theactivegraph.runtimelogger. The line carriesbehavior,event_id,reason,error_type,error_message, anddoc_url. The function- and relation-behavior exception handlers now route through this centralized emitter rather than calling_emit_lifecycledirectly, removing a duplicateERRORlog on the function path. Users opt out via standard Python logging configuration.- Multi-turn tool-use message construction (v1.0.3 #4). When
the LLM returns
tool_useblocks, the runtime appends anLLMMessage(role="assistant", content=raw_text or "", tool_calls=tuple(response_tool_calls))to the message history, not just the raw text. The Anthropic provider adapter's_message_to_anthropicreconstructs the wire-format content blocks (text + tool_use) on the way out. Single-turn flows and zero-tool assistant messages keep their byte-identical wire serialization. Hashing-stability invariant:LLMMessage.to_dict()only emits thetool_callskey when non-None, so existing single-turn fixture prompt hashes are unchanged.
Fixed¶
- First user-test report, finding A —
graph.objects(type="x")AttributeError'd when called on aGraphinstance. Users who'd been writingctx.view.objects(type="x")inside behaviors hit the gap immediately when trying the equivalent outside a behavior. Fixed by addingGraph.objectsas the canonical form. See "Added:Graph.objects(...)". - First user-test report, finding B —
@llm_behavior(output_schema={"type": "object", ...})silently produced zero results: the dict raisedTypeErrorinternally, the runtime emittedbehavior.failedwithreason="llm.schema_violation", and the diagnostic carried no hint that the user had passed a dict instead of a Pydantic class. Fixed by failing at the@llm_behavior(...)line with a structured message + code example. See "Changed:@llm_behavior(output_schema=)strict-validates". - First user-test report, finding C —
runtime.run_goal()returned cleanly with zero results when behaviors failed, leaving users no signal short of inspectinggraph._events. Fixed by emitting a WARNING log line and exposingRuntime.errors. See "Added:Runtime.errors" and "Changed:_emit_behavior_failedemits a WARNING log line". - Second user-test report, finding D — multi-turn tool-use
exchanges through the Vertex AI proxy returned HTTP 400 because
the runtime appended only
raw_textto the message history after a tool_use turn, dropping the tool_use blocks Anthropic's spec requires for matching subsequent tool_result blocks. Fixed by carryingToolCallobjects on the assistant message and reconstructing the content blocks in_message_to_anthropic. See "Added:LLMMessage.tool_calls" and "Changed: multi-turn tool-use message construction".
Examples¶
No example changes in this release. The bundled Diligence pack and the BabyAGI example continue to run unchanged against the new surfaces; their tool-using behaviors exercise v1.0.3 #4's fix through the existing fixture path.
Documentation¶
docs/concepts/graph.mdalready showedgraph.objects(type="claim")as the canonical form; v1.0.3 #1 makes that documentation match the implementation. No prose change required.docs/concepts/failure-model.mdgains an "Observing failures in caller code" section documenting the WARNING log line and theRuntime.errorsproperty as the two user-facing surfaces (v1.0.3 #3).docs/reference/picks up the newBehaviorFailureNamedTuple shape and theRuntime.errorsproperty.
Migration from v1.0.2.post1¶
Forward-compatible. No code changes required.
graph.query(object_type=...)keeps working byte-identically; new code usesgraph.objects(type=...).@llm_behavior(output_schema=SomeBaseModel)keeps working byte-identically. Callers passing a JSON-schema dict will see aTypeErrorat the decorator line instead of a silentbehavior.failedat first LLM call — the error names what they passed and shows the correct form.runtime.run_goal()keeps working byte-identically; the new WARNING log line is opt-out via stdlib logging configuration. Code that inspectedgraph._eventsforbehavior.failedcontinues to work; the newRuntime.errorsproperty is the ergonomic alternative.- Multi-turn tool-use exchanges send extra content blocks on the wire now. Direct Anthropic API access keeps working; Vertex-AI-proxy users stop hitting HTTP 400.
Provider non-promises in v1.0.3¶
Inheriting v1.0.2 / v1.0.1 #5 (c). Specifically unchanged:
LLMProvider.complete()/estimate_cost()/count_tokens()signatures stay locked at v0.6 #3 / v0.7. v1.0.2's additive members (default_model,recognizes_model) are unchanged.- The closed CONTRACT v0.6 #11 reason taxonomy is unchanged.
- OpenAI tool-use translation stays a v1.1 candidate per v1.0.1 #5 (c) clause 2.
- The pack format, the exception hierarchy, and the failure model are unchanged.
[v1.0.2.post1] — 2026-05-19¶
Post-release fix to v1.0.2. The CONTRACT amendment landed in v1.0.2
promised registration-time validation; an external spot-check
discovered the implementation was firing the validation lazily, at
first run_goal() / run_until_idle() / run_until() via
_ensure_registry() rather than at registration time. The
validation logic and error message were correct — the boundary was
wrong.
This post-release moves the validation to both binding moments so cross-provider model mismatches fail fast at setup time:
Runtime(graph, llm_provider=...)construction — runs the bulk validation pass against whatever is already registered (global registry, explicitbehaviors=[...], or pack-loaded).register()/@llm_behaviordecoration — when one or more Runtimes are alive, the freshly-registered behavior is checked against each live Runtime's provider via aweakref.WeakSet. The WeakSet auto-cleans on GC; noRuntime.close()is added.
The lazy path inside _ensure_registry() stays in place as a
defensive double-check for code paths that bypass both binding
moments (currently: pack behaviors registered after Runtime
construction via load_pack). Pack-load-time validation is filed
as a v1.1 candidate if friction surfaces.
The CONTRACT v1.0.2 #1 (b) wording is clarified to match — see that section for the locked decision.
Changed¶
- Validation boundary corrected. No public-API change; the
error message is byte-identical to v1.0.2. The difference is
when it fires: at
Runtime(...)construction or at@llm_behavior/register()time, instead of at firstrun_goal(). Runtime.__init__order updated. Bulk validation runs before the Runtime self-registers in the live-set, so a Runtime whose construction fails validation stays out of the WeakSet. This prevents pytest exception-traceback strong-refs on failed-constructionselffrom polluting subsequent@llm_behaviorvalidation passes during a test session. In production it's a no-op (failed-construction Runtimes go out of scope and are GC'd promptly anyway), but the invariant — "only successfully-constructed Runtimes participate in validation" — is worth keeping clean.tests/conftest.pyclears the live-Runtime WeakSet in the autouse_isolate_registryfixture for the same pytest-pollution reason. The WeakSet auto-cleans on GC in production; only test isolation needs the explicit clear.
Added¶
activegraph/runtime/_live.py— new module owning the live- Runtime WeakSet, thetrack_runtime()hook, and the single-behavior cross-provider validator that both binding moments invoke. The validator is factored out of v1.0.2's_resolve_and_validate_llm_modelsso the check itself lives in one place; the bulk function and the new decorator-path call site both delegate.
Fixed¶
- External spot-check finding —
Runtime(graph, llm_provider=...)no longer returns successfully when the registry contains a cross-provider model mismatch. The@llm_behaviordecorator no longer adds a conflicting behavior to the registry when a Runtime with an incompatible provider is already alive. Same diagnostic message; earlier boundary.
Migration from v1.0.2¶
No code changes required. The error fires at an earlier point in
the program's execution — what used to surface at rt.run_goal(...)
now surfaces at Runtime(...) or at @llm_behavior decoration.
Code that was already catching InvalidRuntimeConfiguration
around run_goal should move the try/except to the relevant
binding moment, or wrap the whole setup block.
[v1.0.2] — 2026-05-19¶
Patch release addressing the most urgent of three findings from the second-round external user-test. The framing — patch release based on user-test findings — matches v1.0.1's; the scope is narrower (one finding, not four, plus no provider-expansion work). The other two findings need design consideration and are tracked for v1.0.3 / v1.1, not folded into this release.
The finding (v1.0.1 #5 credibility hit)¶
v1.0.1 #5 shipped OpenAIProvider and locked the provider-
commitment contract: same Protocol surface, swap one for the other
without reshaping any @llm_behavior. The second-round user-test
exercised exactly that swap and surfaced a silent default-model
mismatch: @llm_behavior(...) without an explicit model=
inherited the decorator's hardcoded default "claude-sonnet-4-5"
— an Anthropic-family name. With Runtime(graph,
llm_provider=OpenAIProvider()), that name went verbatim to
OpenAI's chat.completions.create and produced an HTTP 404 with
no hint that the cross-provider mismatch was the cause. The
behavior.failed event carried the provider's verbatim 404 prose;
diagnosis required inspecting the decorator, tracing the default,
and recognizing the model-family conflict.
This directly undermined v1.0.1's provider-agnostic claim. v1.0.2 makes the default provider-aware and validates explicit model names at registration time.
Added¶
LLMProvider.default_modelattribute (additive Protocol widening, CONTRACT v1.0.2 #1 (a)). Each shipped provider declares a default model:
| Provider | default_model |
|---|---|
AnthropicProvider |
"claude-sonnet-4-5" |
OpenAIProvider |
"gpt-4o-mini" |
@llm_behavior(...) with no model= argument now resolves to
the configured provider's default_model at registration time,
via Runtime(graph, llm_provider=...)._ensure_registry(). The
resolved name is stamped onto the LLMBehavior instance so
behavior.build_prompt(...) sees the concrete model in its
hash inputs.
- LLMProvider.recognizes_model(name) -> bool method (additive,
CONTRACT v1.0.2 #1 (b)). Returns True when name belongs to
a model family the provider serves. Shipped providers:
AnthropicProvider recognizes claude-*; OpenAIProvider
recognizes gpt-*, o1-*, o3-*, o4-*.
Changed¶
@llm_behavior(model=...)default changed from"claude-sonnet-4-5"toNone. Existing call sites passingmodel="..."explicitly stay byte-identical. Call sites that omittedmodel=previously inherited"claude-sonnet-4-5"via the decorator default; they now inherit the same string when the configured provider isAnthropicProvider(whosedefault_modelis"claude-sonnet-4-5"), and the provider- appropriate default ("gpt-4o-mini") when the configured provider isOpenAIProvider. Custom providers that don't declare adefault_modelretain the v1.0.1 hardcoded fallback for backward compat. CONTRACT v1.0.2 #1 (c).LLMBehavior.modelfield type changed fromstrtoOptional[str]. v1.0.1 instances pickle/load cleanly (string values still load); freshly-decorated v1.0.2 behaviors carryNoneuntil a Runtime resolves a provider default. CONTRACT v1.0.2 #1 (c).- Registration-time cross-provider validation. When a behavior
pins
model=explicitly and the configured provider doesn't recognize the name, the runtime checks each shipped provider'srecognizes_model()and raisesInvalidRuntimeConfigurationif a different shipped provider claims the name. The error is structured per the v1.0 format —what_failed/why/how_to_fix— and names both providers plus the way out (swap the provider, or use the configured provider's default). Permissive by default: unknown names (custom deployments, fine-tunes likeft:gpt-4o-mini:org::id) pass through silently. CONTRACT v1.0.2 #1 (b). docs/reference/llm-providers.mdgains a "Default model resolution" section, a "Cross-provider model-name validation" section, and updated rows fordefault_modeland recognized prefixes in the side-by-side table. The "Writing a custom provider" example now shows the optionaldefault_model+recognizes_modelmembers and notes they are additive.
Fixed¶
- Second external user-test, finding 1 —
@llm_behaviorwith nomodel=argument silently used an Anthropic-family default, producing HTTP 404 at first LLM call when the configured provider wasOpenAIProvider. The diagnostic message onbehavior.faileddid not name the cause. See "Added:LLMProvider.default_model" and "Changed: registration-time cross-provider validation" above.
Examples¶
examples/babyagi.pysimplified to drop its per-provider model table. The@llm_behaviordefinitions omitmodel=and letRuntime's provider resolution pick the right default. Switching--providerbetweenanthropicandopenaiis a one-line change in the example; nothing else needs to change.
Provider non-promises in v1.0.2¶
Inheriting the v1.0.1 #5 (c) clauses. Specifically unchanged in v1.0.2:
LLMProvider.complete()/estimate_cost()/count_tokens()signatures stay locked at v0.6 #3 / v0.7. v1.0.2 widens the Protocol additively with two members; it does not reshape the three core methods.- The closed CONTRACT v0.6 #11 reason taxonomy is unchanged. The
cross-provider mismatch raises a
ConfigurationErrorsubclass at registration time, not a new behavior-failure reason code. - The v1.0.1 #2 prompt-assembly shape is unchanged. Same schema
- example instance + "instance not schema" language.
Migration from v1.0.1¶
Additive. Forward-compatible:
@llm_behavior(model="...")call sites that pinned an explicit string keep working byte-identically.@llm_behavior(...)call sites that omittedmodel=now inherit the configured provider'sdefault_model. ForAnthropicProvider, that's the same"claude-sonnet-4-5"the v1.0.1 decorator default produced. ForOpenAIProvider, the default changes from the v1.0.1 silent-Anthropic-name to"gpt-4o-mini", fixing the v1.0.2 finding.- Custom providers that don't declare
default_modelcontinue to use the v1.0.1 hardcoded fallback. Custom providers that want the v1.0.2 default-resolution behavior add adefault_model: str = "..."class attribute (and optionally arecognizes_model()method to participate in cross-provider validation). LLMProviderProtocol gains two additive members. Existing custom-provider classes that don't implement them still passisinstance(p, LLMProvider)checks at the three core methods.
[v1.0.1] — 2026-05-19¶
The first-external-user-test patch plus the OpenAI provider expansion. v1.0 final shipped on 2026-05-18; the first developer outside the maintainer's loop ran the install / quickstart / tutorial path on the day-of, and three small UX findings surfaced before v1.0.1 publish. All three fit the "X is confusing" shape on HANDOFF.md's user-test heuristic (none "X doesn't compose with Y the way I expected" — architectural shape held).
v1.0.1 also closes an implicit adoption-surface gap the user-test
didn't surface but readers feel: the framework shipped a single
concrete LLMProvider (AnthropicProvider), making the
provider-agnostic claim read as theoretical. v1.0.1 #5 ships
OpenAIProvider with surface parity and locks in the
provider-commitment contract.
No CONTRACT amendments to v1.0's own decisions, no public-API renames, no new runtime capability. CONTRACT v1.0.1 records the four user-test fixes plus the provider-expansion decision; this entry is the shipping changelog.
Added¶
activegraph.register(behavior_obj)— public function for appending an already-constructed behavior to the global registry. Pairs withclear_registry()for multi-run scripts that capture the registry once and re-register per run, replacing the v1.0 pattern of reaching into the privateactivegraph.behaviors.decorators._REGISTRYlist. Validates the argument is aBehavior/RelationBehavior/LLMBehaviorinstance and raisesTypeErrorotherwise. CONTRACT v1.0.1 #1.docs/cookbook/multi-run-scripts.md— new cookbook recipe covering the capture-once-re-register-per-run pattern, when to use it (hypothesis sweeps, A/B comparisons inside one process, batch jobs that want per-input graph isolation without per-input process startup), and when not (single-runtime scripts don't need any of this). Wired into the mkdocs nav under the Cookbook section. CONTRACT v1.0.1 #1.activegraph.llm.prompt.example_instance_from_schema— new helper that walks a JSON Schema and produces a deterministic placeholder instance. Used bybuild_system_promptto render an example alongside the schema in the LLM system prompt; exported for tests and for prompt-debugging tools. CONTRACT v1.0.1 #2.
Changed¶
@llm_behavior(output_schema=...)system prompt now embeds an example instance and explicit "instance, not schema" language. The first external user-test surfaced a failure mode v1.0's prompt-assembly didn't anticipate: some models echo the JSON Schema definition back as their response instead of an instance that conforms to it. The framework refused withllm.schema_violation, the user had to reverse-engineer the cause from the raw response. v1.0.1 changes the system-prompt schema block to three parts — the schema (unchanged), a synthesized example instance, and explicit "Return an INSTANCE that conforms to this schema, NOT the schema itself" language.build_instruction(the user-message task sentence) also gains "NOT the schema definition itself" so the framing appears in two places. The example generator handlestype,properties,items,enum,const,anyOf/oneOf(picks the non-null variant), and$refto$defs/definitions; unrecognized shapes fall back tonull. The synthesized example is deterministic across runs so the prompt-hash cache key stays stable. CONTRACT v1.0.1 #2. See the expandedllm-behavior-errorreference page for the failure mode and theprompt_template=override pattern when the auto-derived example isn't useful.SQLiteEventStore()constructor error points at the higher- levelRuntime(graph, persist_to=...)API. v1.0 raised a bare PythonTypeError: missing 1 required positional argument: 'run_id'; the user-test reader had to first look up "what is a run_id" before they could decide how to recover. v1.0.1 hand-raises a TypeError with a structured hint:
SQLiteEventStore requires a run_id. For most cases, use
Runtime(graph, persist_to='path/to/trace.sqlite') instead,
which handles run_id automatically. If you need a per-run
handle (migration, conformance test, trace inspection), pass
both explicitly: SQLiteEventStore('path/to/trace.sqlite',
run_id='run_...').
The signature change (Optional[str] = None for both args) is
internal — every existing caller passes both args positionally
or by keyword. CONTRACT v1.0.1 #3.
- clear_registry() returns the cleared list. v1.0 returned
None; v1.0.1 returns list[Behavior | RelationBehavior] in
registration order. Callers that ignored the return value still
work unchanged. The shape pairs with the new register() for
the multi-run pattern. CONTRACT v1.0.1 #1.
- @llm_behavior decorator docstring names what each
prompt_template= placeholder contains. v1.0 documented the
four placeholders by name ({system}, {view}, {event},
{instruction}) but didn't say what each one rendered to. The
v1.0.1 doc-site entry for the schema-echo failure mode points
readers at prompt_template= as a fallback for schemas the
auto-example can't render usefully, so the decorator docstring
grows a four-bullet list naming the content of each placeholder.
Concrete enough to compose a custom template without first
opening activegraph/llm/prompt.py. CONTRACT v1.0.1 #4.
Fixed¶
- First external user-test, finding 1 — multi-run scripts had
no public-API path to re-populate the registry after
clear_registry(). See "Added:activegraph.register" and "Changed:clear_registry()returns the cleared list" above. - First external user-test, finding 2 — models occasionally
returned the JSON Schema definition as their response instead of
an instance, triggering
llm.schema_violation. See "Changed:@llm_behavior(output_schema=...)system prompt now embeds an example instance" above. - First external user-test, finding 3 —
SQLiteEventStore()with missing args produced a bare PythonTypeErrorinstead of hinting at the higher-levelpersist_to=API. See "Changed:SQLiteEventStore()constructor error" above.
Provider expansion¶
activegraph.llm.OpenAIProvider— second concreteLLMProviderwith surface parity toAnthropicProvider. Same three Protocol methods (complete,estimate_cost,count_tokens), same lazy-SDK + env-var loading shape, same family-prefix pricing table, same structured-output path through the framework's instruction-based prompt assembly. A runtime swappingAnthropicProvider()forOpenAIProvider()doesn't reshape any@llm_behaviordefinition. CONTRACT v1.0.1 #5.activegraph.llm.parsing.parse_structured_response— JSON-extraction-then-Pydantic-validate helper extracted fromAnthropicProvider. Both shipped providers (and any future provider that uses the framework's instruction-based path) import it directly, producing byte-identicalllm.parse_errorandllm.schema_violationreason codes for byte-identical responses. The extraction preserved Anthropic's behavior exactly; all 9 existingtest_llm_anthropic.pytests pass unchanged. CONTRACT v1.0.1 #5.pyproject.tomlextras follow a three-pattern shape.[llm]pulls every shipped provider's SDK (anthropic>=0.40,openai>=1.0,tiktoken>=0.7).[anthropic]and[openai]aliases install one provider at a time for cost-conscious production deployments.[all]rolls up everything from[llm]plus persistence and metrics extras. CONTRACT v1.0.1 #5 (b).docs/reference/llm-providers.md— new reference page documenting both providers side-by-side: install commands, API key env vars, the symmetric Protocol surface, the asymmetric details (count_tokensserver-side vs client-side, tool-use support gap, native structured-output mode deferral), and a "writing a custom provider" section pointing atparse_structured_responsefor error-semantics parity. Wired into the mkdocs nav under Reference. CONTRACT v1.0.1 #5.
Provider non-promises in v1.0.1 (per CONTRACT v1.0.1 #5 (c))¶
Documented as contract clauses rather than discovered as user friction later — same discipline as v1.0's honesty section.
- Token counting is provider-dependent. Anthropic uses
messages.count_tokensserver-side; OpenAI usestiktokenclient-side when available and achars / 4heuristic when not, with a one-time debug log on first heuristic call. Operators gating onbudget.max_cost_usdshould installtiktoken(via[openai]or[llm]) for accurate accounting. - Tool use is Anthropic-only in v1.0.1.
OpenAIProvideraccepts thetools=kwarg for Protocol compatibility but raisesLLMBehaviorError(reason="llm.network_error")with a v1.1 pointer when the list is non-empty. Tool-shape translation inTool.to_definition()is filed under v1.1 #7-and-beyond. - Native structured-output modes are v1.1 candidates. Both
providers use the instruction-based path that v1.0.1 #2's
example-instance work feeds. OpenAI's
response_format={"type":"json_schema",...}and analogous future modes stay v1.1 — they diverge providers' latency profiles, cache-key semantics, and error paths in ways that warrant their own decision. - No new reason codes. The closed CONTRACT v0.6 #11 taxonomy
is unchanged. OpenAI auth failures land in
llm.network_errorwith the exception message preserved verbatim, same as Anthropic for the same failure mode.
Examples¶
examples/babyagi.pywith companionexamples/babyagi/README.md— BabyAGI's autonomous agent loop (Nakajima 2023) rebuilt as three reactive behaviors over a shared graph. The minimal-loop counterpart to the Diligence pack's domain-rich example: same conceptual lineage as the framework's launch essays, runnable end-to-end against either provider, traces totraces/babyagi-<timestamp>.sqlite. The v1.0.1 publicregister()API replaces v1.0's_REGISTRYworkaround. A--provider {anthropic,openai}CLI flag exercises the new symmetric surface — same example, same loop, swap the provider with one argument.
Migration from v1.0¶
Additive. The changes are forward-compatible:
clear_registry()now returns a list; v1.0 callers that ignored the return continue to work unchanged.register()is new; nothing existing calls it.SQLiteEventStore("/path", run_id="r")(the v1.0 supported shape) keeps working; the new error fires only on missing-arg call sites, which are by construction unmigrated.- LLM prompt-hash values change because the system prompt got
longer. No on-disk LLM fixtures exist in the framework's tests,
so no replay-divergence risk for in-tree code; user code that
saved fixtures from a v1.0 live run will see a fresh
llm.fixture_missingagainst v1.0.1 prompts and needs a re-record pass. Same shape as any v0.6+ prompt-assembly change; seellm-behavior-error's re-record recipe. OpenAIProvideris new; install viapip install "activegraph[openai]"orpip install "activegraph[llm]". Existing[llm]extra users pick upopenaiandtiktokenautomatically on upgrade alongside the existinganthropicSDK.
[v1.0] — 2026-05-18¶
v1.0 final. The lighter-weight verification pass against v1.0-rc3 ran the same seven-check shape as the rc2 lighter pass and produced six clean passes plus one partial finding on Check 6 (the tutorial's step 7 fork-and-diff snippet undersold its own output). The B2 fix's core promise — fork-and-diff runs without an API key against bundled fixtures — held intact. Scope = v1.0-rc3 + the Check 6 tutorial fix + a README "Concepts at a glance" section bridging the README and the doc site for evaluators.
No runtime capability changes; no public-API renames; no CONTRACT amendment.
Changed¶
- Tutorial step 7 fork-and-diff snippet emits its own next-step
guidance instead of a bare
forked: <run-id>line. The shipped rc3 snippet ran cleanly end-to-end but its terminal output was one anticlimactic line, leaving a first-time reader to scroll past it and notice the "Then diff:" CLI block on their own. The snippet now prints the exactactivegraph diffcommand for the fork it just created (parameterized from the same constants defined at the top of the snippet), and the transition prose before the CLI block names it explicitly. The diff itself produces 61 divergent objects and 49 divergent relations — a substantive output that the rc3 snippet was hiding behind a prose-only handoff. The voice test: a first-time reader runningpython fork_and_diff.pycold now sees both the fork creation and the exact next command, with no ambiguity about whether they need to do something else. - README adds a "Concepts at a glance" section between
"What you get" and "A small example." Twelve primitives — graph,
events, behaviors, relations, patches, views, frames, policies,
patterns, replay, forking, failure model — each with a one-line
"what + why" and a link to the concept page. The section is
evaluator-facing: it lets a reader scan the framework's
conceptual primitives from the GitHub repo page without first
clicking through to the doc site. Mirrors the
docs/concepts/*.mdnavigation 1:1; complements but does not duplicate "What you get" (which is feature-oriented; the new section is primitive-oriented). - Deploy-verification workflow gains a
pull_requesttrigger. Discovered at v1.0 final merge time: the gate (CONTRACT v1.1 #9) ran on push-to-main and cron but not on PR events, so the required-status-check rule on branch protection had nothing to match against on the PR. Aworkflow_dispatchrun reported under a different context name and didn't satisfy the rule. Addingpull_request:to the workflow'son:triggers lets the check report alongside the other CI gates on every PR; the existing push and cron triggers continue to run unchanged. Config-only change; the gate's logic is untouched.
Fixed¶
- Check 6 user-test finding (rc3 lighter pass). See the Tutorial step 7 entry above. The runtime artifact didn't change; the tutorial prose and the snippet's terminal output changed.
Migration from v1.0-rc3¶
Additive. No code changes required. Existing v1.0-rc3 installs should:
[v1.0-rc3 amendment — docs-build fix] — 2026-05-18¶
Post-rc3-merge, pre-rc3-tag follow-on. The v1.1 #9 deploy-
verification gate's first run on main after the rc3 merge caught
a silent failure that had been dwelling since the doc-site phase:
mkdocs.yml declared the mkdocstrings plugin for API-reference
auto-generation (added in commit b533dd4 during the doc-site
phase), but .github/workflows/docs.yml's hardcoded install step
only pulled mkdocs + mkdocs-material. Every docs build through
every doc-site PR failed with Config value 'plugins': The
"mkdocstrings" plugin is not installed, the deploy job never had
an artifact to upload, and the has_pages: false finding from the
rc3 #2 investigation was an effect of this — not just the
externally-owned Pages-enable step. No version bump; this is a
follow-on to v1.0-rc3, not a new rc.
The gate did its job¶
CONTRACT v1.1 #9 (deploy-verification) was designed to catch the
class "internal CI ships green, the external artifact is broken,
nobody notices for months." On its first real run, it caught the
build failure that v1.0 had been carrying silently since the doc-
site phase. The gate's red signal on main post-rc3-merge was
correctly the discipline call to action, not noise.
Fixed¶
mkdocstrings[python]now installed by the docs workflow. Root cause: hardcodedpip install mkdocs mkdocs-materialin.github/workflows/docs.ymldrifted frommkdocs.yml'splugins:block. Audit-then-fix discipline (same shape as rc3's wheel-completeness audit): enumerated every plugin and every markdown extension inmkdocs.yml, cross-checked against the workflow install. Single gap:mkdocstrings(+ its[python]handler). Thepymdownx.*extensions are covered transitively bymkdocs-material(verified via fresh-venv install).
Changed¶
pyproject.tomlgains adocsoptional-dependency extra. Listsmkdocs,mkdocs-material,mkdocstrings[python]. The docs workflow now installs from this extra (pip install -e ".[docs]") instead of hardcoding the dep list. Adding a new mkdocs plugin in the future updates one place — the same pattern every other workflow already follows (types.yml,docstrings.yml,wheel-completeness.yml, anddeploy-verification.ymlall install from pyproject).
Audit-decision: no CONTRACT v1.1 #10¶
Considered: a generalized "declared-but-not-installed" audit gate
in the same shape as v1.1 #8 and v1.1 #9. Cross-checked all six
workflows' install steps. docs.yml was the only workflow
whose install step hardcoded deps that could drift from a separate
config file (mkdocs.yml). Every other workflow installs from
pyproject extras (auto-synced) or installs only its own tool, so
the failure mode "config declares X, install doesn't include X" is
structurally impossible for them. Joining docs.yml to that
pattern institutes the prevention; an audit gate would be
mechanism without a target. Filed as a one-off, not a class.
Externally owned (unchanged from rc3)¶
The two operational steps named in the rc3 entry above still
gate the v1.1 #9 deploy-verification check from passing: enable
GitHub Pages on the repo, configure DNS for docs.activegraph.ai.
With this build-fix landed, the docs workflow can now produce a
deployable artifact for the first time since the doc-site phase
shipped — the artifact is the precondition that the externally-
owned Pages-enable step then publishes.
[v1.0-rc3] — 2026-05-18¶
The lighter-user-test-findings milestone. Two findings from the CONTRACT v1.0 #C4 lighter pass against rc2 addressed; both surfaced discipline gaps that ship with new v1.1 CI gates institutionalizing the verification layer that caught them. No runtime capability changes; no public-API renames.
Added¶
- Wheel-completeness CI gate
(
.github/workflows/wheel-completeness.yml+tests/test_wheel_completeness.py). CONTRACT v1.1 #8. Builds the wheel viapython -m build, installs it into a fresh venv (NOT editable), runsactivegraph quickstartagainst the installed wheel, fails if any runtime data file is missing. Catches the class of bug that's structurally invisible to source-tree tests and editable installs. Markedslow; CI invokes viapytest -m slow tests/test_wheel_completeness.py. To be configured as a required status check on main per CONTRACT v1.1 #8 implementation scope. - Deploy-verification CI gate
(
.github/workflows/deploy-verification.yml+tests/test_doc_site_reachable.py). CONTRACT v1.1 #9. FetchesDOCS_BASE_URL+ 4 known-good page paths, asserts HTTP 200 and that the response body containsActive Graph(the mkdocssite_name). Failure-mode design distinguishes DNS failure, HTTP 404, and content mismatch — each fails with a message that names the operational step to fix it. The HTTP-reachability complement totests/test_doc_links.py, which is source-tree- scoped only. Runs on push to main + daily cron (catches drift if the site goes down without a code change). Required for merge once GitHub Pages is enabled.
Changed¶
- B3 fix:
prompts/*.mdship in the wheel. The v1.0-rc2 user- test gate surfaced thatpip install activegraph==1.0.0rc2followed byactivegraph quickstartcrashed withPackPromptLoadError: prompts directory does not exist. Root cause:pyproject.tomldeclared no[tool.setuptools.package-data]block, so setuptools' default behavior (ship only.pyfiles) omitted the 4.mdprompt files. Audit confirmed exactly 4 non-.pyfiles inactivegraph/, all inpacks/diligence/prompts/. Fix is a single key/glob; rc3 #1 commit. The wheel-completeness gate above is the enforcement layer that prevents this class from recurring. - Domain cutover:
docs.activegraph.dev→docs.activegraph.ai. CONTRACT v1.0 #C6 amended (rc3 amendment block in CONTRACT.md). Primary domain switched to the already-owneddocs.activegraph.ai..devbecomes a redirect-source the maintainer registers and configures separately (externally owned). The codebase holds exactly one primary; the constantDOCS_BASE_URLis the single swap point. Affected files:activegraph/errors.py(constant),docs/CNAME,mkdocs.yml(site_url),README.md(5 link refs),CHANGELOG.md(11 link refs),HANDOFF.md,docs/about/publishing.md,examples/quickstart_session.txt,.github/workflows/docs.yml(comments). Error snapshots rebaselined viaUPDATE_SNAPSHOTS=1— 58 files. The cutover pattern worked as designed: one constant change propagates to every URL throughf"{DOCS_BASE_URL}/..."interpolation.tests/test_doc_links.pycontinues to recognize the .dev URL form so historical CHANGELOG entries linking to .dev still pass the source-presence check.
Externally owned (B4 findings; the gate is shipped, the¶
operational steps are yours):
- GitHub Pages must be enabled on the repo. Per rc3 step-4
investigation:
has_pages: falseis the smoking-gun finding; the doc site at any URL 404s because there's nothing to serve. Fix: Settings → Pages → Source: GitHub Actions. Precondition: the repo must be public (free plan) or on GitHub Pro/Team /Enterprise (paid). Until this lands, the v1.1 #9 deploy- verification gate is red on every CI run; that's the correct signal. - DNS for
docs.activegraph.aimust be configured. CNAME record pointing atyoheinakajima.github.io(the GitHub Pages default subdomain). The v1.1 #9 gate's failure message names this when DNS is the missing piece. docs.activegraph.devredirect. Register the .dev domain and configure it to 301/302-redirect todocs.activegraph.ai. Not strictly required for the v1.1 #9 gate to pass (gate only checks .ai), but needed for historical CHANGELOG entries' .dev links to keep resolving for users.
Boundary shift (CONTRACT v1.1 framing):¶
The two new gates each move the user-test boundary outward by one layer.
- v1.1 #8 (wheel-completeness): after this gate lands, the lighter user-test verifies the PyPI artifact (CDN, upload, distribution) — not the wheel itself.
- v1.1 #9 (deploy-verification): after this gate lands, the lighter user-test verifies the published-domain experience (does the README link land on a page that reads well? does navigation feel right?) — not the basic reachability question of "does this URL return 200."
Same rc1-vs-rc2 discipline pattern noted in the CONTRACT entries: each rc surfaces a finding that's structurally invisible to the prior layer's CI; each rc institutionalizes the verification layer that caught it.
[v1.0-rc2] — 2026-05-18¶
The user-test-findings milestone. Five findings from the CONTRACT v1.0 #C4 gate addressed; one was a latent runtime state-machine bug since v0.5. No new runtime capability; no public-API renames.
Added¶
- PyPI publish workflow (
.github/workflows/publish.yml). Tag-push trigger matchingv*triggerspython -m buildthen upload via PyPI trusted publishing (OIDC-based). Documented in Publishing a release. Externally owned per CONTRACT v1.0 #C8 — the agent ships the workflow, the maintainer runs the publish. - Tutorial-snippet CI test (
tests/test_tutorial_snippets.py). Subprocess-runs the tutorial's step 7 fork snippet end-to-end against the bundled fixtures; asserts exit 0 and idempotency on re-run. Tactical down-payment on CONTRACT v1.1 #2 expansion (spec-vs-impl drift gate for Python doc snippets). _requeue_unfiredregression test (tests/test_requeue_unfired.py). Locks the C3 regression vector:Runtime.loadon a cleanly-drained saved run producesqueue_depth == 0.
Changed¶
_requeue_unfiredusesruntime.idleas the high-water mark. Latent bug since CONTRACT v0.5 #8: the function relied on the false reverse-implication "nobehavior.startedreferences this event id ⟹ event was still in the queue." Events with zero subscribed behaviors are popped-and-discarded with nobehavior.startedemitted, so they were falsely requeued on everyRuntime.load. The fix uses the lastruntime.idleevent as the high-water mark (the runtime emitsruntime.idleonly after the queue empties); only events after the last idle are candidates for requeue.runtime.budget_exhaustedis explicitly NOT a drain marker — using it would break budget-bounded pause-and-resume.- Tutorial step 3 and quickstart prose distinguish the provider
layer (where the fixture provider produces responses) from the
runtime's replay cache layer (where
cache_hit=truelegitimately appears under strict-replay loads orRuntime.fork()in-process). Pre-rc2 prose conflated the two. The conflation was originally in the v1.0 spec atexamples/quickstart_session.txt; the spec is updated with a header drift-note documenting the two-layer reality. - Tutorial step 7 fork snippet uses
RecordedDiligenceProvider(companies=THREE_COMPANIES)as the fork'sllm_provider=. Matches the parent run's provider; preserves the "no API key required" tutorial pitch. The snippet also includes a tutorial-only cleanup-on-collision branch so it's re-runnable without manual DB surgery. _prepare_interactive_subdircollision prompt re-prompts on unrecognized input. Pre-rc2 behavior fell through to the suffix branch on any input that wasn'toorq, which swallowed typeahead from the next prompt. Mirrors the existing iteration-loop pattern atrun_interactive_mode.
Deprecated¶
Nothing. Backward compatibility holds — all v0–v1.0-rc1 tests pass.
Removed¶
Nothing user-facing.
Fixed¶
Runtime.load(...).status().queue_depthreads 0 on a freshly loaded cleanly-drained run. Was a non-zero false count of events that had been popped-and-discarded during the original run. See the Changed entry for_requeue_unfiredabove.activegraph --versionreports the correct version. Was stuck at0.9.1through v1.0-rc1's release (the version-sync gate validated internal consistency but not correspondence with the git tag). The v1.1 #6 version-tag-correspondence gate closes this gap in v1.1.- Tutorial step 7 fork snippet runs end-to-end against bundled fixtures with no API key, matching the rest of the quickstart's "no API key required" pitch.
cache_hit=trueclaim in the tutorial and quickstart "what just happened" prose: the claim was wrong for initial fixture-mode runs (the runtime's replay cache only fires on strict-replay or in-process fork). Prose corrected; two-layer vocabulary lands cleanly for first-time readers.
Migration from v1.0-rc1¶
Additive. No code changes required. Existing v1.0-rc1 installs should:
Existing saved runs (*.db files) load with queue_depth == 0
correctly post-upgrade — the C3 fix is in the load path, not the
storage format.
Known follow-ons (v1.1 scope)¶
In addition to the v1.0-rc1 v1.1 backlog (carried forward):
- CONTRACT v1.1 #2 expansion — spec-vs-impl drift gate covers executable Python snippets in docs, not just CLI flags. v1.0-rc2 ships the tactical step 7 test; v1.1 generalizes.
- CONTRACT v1.1 #5 —
Runtime.loadauto-provider ergonomics. The rc2 fix for B2 passesRecordedDiligenceProviderexplicitly; the v1.1 design question is whetherRuntime.loadshould infer a provider from the run's recorded events or from a pack-manifest declaration. New runtime capability, banned in v1.0. - CONTRACT v1.1 #6 — version-tag-correspondence CI gate.
Existing version-sync gate validates
__version__matchespyproject.toml; the v1.1 gate adds correspondence with the current annotated tag in tagged-release CI runs. - CONTRACT v1.1 #7 backlog item: fork cache pre-population
symmetry.
Runtime.fork(at_event=...)in-process pre-populates the LLM cache from the parent's events; the persistent shape (SQLiteEventStore.fork_runthenRuntime.load) does not. The two paths should be symmetric. New runtime behavior, banned in v1.0.
[v1.0-rc1] — 2026-05-18¶
The adoption-surface milestone. No new runtime capability; the contract is "a new user can install, run, understand, debug, and extend the framework without reading source code."
Added¶
- Error hierarchy rewrite. Every exception now inherits from
ActiveGraphErrorand carries structuredwhat_failed,how_to_fix, andcontextfields. Seven category bases (ConfigurationError,RegistrationError,ExecutionError,ReplayError,StorageError,PatternError,PackError) with 33 leaves. Built-in lineage preserved via multi-inheritance —except ValueError/except KeyErrorclauses still work. - Per-error reference catalog. Every error message ends with a
More:link to a dedicated page documenting when it fires, why, how to diagnose, and how to fix. Catalog at docs.activegraph.ai/reference/errors. - Documentation site at docs.activegraph.ai: concepts pages for every primitive (graph, events, behaviors, relations, patches, views, frames, policies, patterns, replay, forking, failure model); guides; cookbook (common patterns, debugging, migration); CLI reference; API reference via mkdocstrings.
activegraph quickstartCLI command. Bundled Diligence demo in fixture mode (byte-deterministic, no API key, ~20 seconds);--interactivemode walks the user through writing their first behavior.- 10-minute tutorial at docs.activegraph.ai/quickstart. Install → run → write a behavior → save and inspect → fork and diff. Seven steps; every example runs.
- CI gates on the public surface. Version-sync gate
(
pyproject.toml↔activegraph.__version__), broken-link gate for the doc site, mypy--strictgate on the__all__allowlist (22/38 modules clean at baseline), docstring coverage gate (Ring 0 92/100 not-missing, Ring 1 at 84.7%; exemption list indocstring_gaps.toml). - CLI follow-on flags (referenced from error messages' recovery
prose):
inspect --event <id>,inspect --behaviors,inspect --pack-version,migrate --skip-corrupted,fork --record.
Changed¶
- README trimmed from 1275 lines to ~190. The doc site is now
the canonical reference; the README is the conversion funnel
(30-second pitch → install →
activegraph quickstart→ tutorial). - Error messages structured. Every framework-raised exception
exposes
what_failed(one line),how_to_fix(actionable prose), andcontext(structured detail) on the exception instance. Plainstr(exc)renders all three. - Trace printer formats
pack.loaded(was previously falling through to the generic event renderer).
Deprecated¶
Nothing. Backward compatibility holds — all v0–v0.9 tests pass.
Removed¶
Nothing user-facing. Internal: a handful of dead code paths surfaced during the error-rewrite audits were removed.
Fixed¶
pack.loadedtrace formatting (was missing despite being spec'd in CONTRACT v0.9 #25).- Several inconsistent error categories — see CONTRACT v1.0 PR-F audit findings for the cross-category reclassifications.
Migration from v0.9.1¶
Additive. See Migration from v0.7 § 5–6:
# v1.0 — broader catches with structured context:
try:
rt = Runtime.load(url, run_id=rid)
except activegraph.StorageError as e:
log(e.what_failed, e.how_to_fix, e.context)
except activegraph.ActiveGraphError as e:
log(e.what_failed, e.how_to_fix)
Existing except ValueError/except KeyError/except TypeError
clauses keep working — multi-inheritance preserves builtin
lineage.
Known follow-ons (v1.1 scope)¶
fork --set <pack>.<key>=<value>for cheap fork-with-override experiments (CONTRACT v1.1 #1; canonical Python-API recipe at Cookbook § Fork with a pack-setting override).inspect --memoandinspect --search(CONTRACT v1.1 #1).- Type-completeness burndown — close the 16 dirty allowlist
modules (CONTRACT v1.1 #3,
TYPE_REPORT.md). - Docstring-completeness burndown — close the 8 missing Ring 0
exemptions and upgrade one-liners to full
(CONTRACT v1.1 #4,
COVERAGE_REPORT.md). - Spec-vs-impl drift gate for CLI flags (CONTRACT v1.1 #2).
[v0.9.1] — 2026-05-17¶
Operator-visible quality-of-life fixes between v0.9 and v1.0.
Added¶
[trace.flags]rollup header at the top of every trace block withprompt_normalized=true|falseso operators can see at a glance whether a run used the v0.7+ normalized-prompt format.
Changed¶
- Approval-demo output is now granular (per-object) rather than batched, so operators can see which approval the runtime is waiting on.
Migration from v0.9¶
None — additive trace and demo improvements; no API changes.
[v0.9] — 2026-05-16¶
The pack format milestone. A pack bundles object types, behaviors, tools, prompts, and policies for a specific domain.
Added¶
Packdataclass: frozen, equality by(name, version).- Pack-aware decorators (
activegraph.packs.behavior,llm_behavior,relation_behavior,tool) with no global registry side effects. runtime.load_pack(pack, settings=...)— idempotent; conflicts (object type, relation type, behavior name, tool name, policy name) raisePackConflictErrorbefore any state mutation; version mismatch raisesPackVersionConflictError.- Object type schemas enforced via Pydantic at
graph.add_object; relation type validation atgraph.add_relation. - Namespace prefixing: canonical strict
(
diligence.claim_extractor); short-name lookups lenient. - Three settings access forms: typed parameter injection
(primary),
ctx.settings,ctx.pack_settings(name). - Prompt loader: TOML frontmatter; content-hashed via SHA-256 truncated to 16 hex chars; hash (not version) is the replay contract.
- Discovery via Python entry points
(
activegraph.packs);discover(),load_by_name(),clear_discovery_cache(). activegraph pack new <name>scaffolding command.activegraph pack listto enumerate installed packs.activegraph.packs.diligence— production-quality reference pack: 8 object types, 6 relation types, 7 behaviors, 3 tools, 2 policies, 4 prompts, recorded fixtures for 3 companies, end-to-end demo atexamples/diligence_real_run.py.- Pack authoring guide at Authoring packs.
Changed¶
- Python floor raised to 3.11 (uses stdlib
tomllib). pydantic>=2is now a hard dependency (was opt-in via[llm]). The pack format's object-type schemas and settings models require it.click>=8,<9becomes a hard dependency (CLI is always available).
Migration from v0.8¶
Additive. See
Migration from v0.7 § 4.
Global decorators (@behavior, @tool) keep working alongside
loaded packs; the pack format is opt-in for new code.
Python 3.10 users must upgrade to 3.11+ before installing v0.9.
[v0.8] — 2026-05-16¶
The operator surface milestone. Hardens the boundary between the framework and the world it runs in.
Added¶
PostgresEventStorebehind the sameEventStoreprotocol as SQLite (Postgres 16+;pip install activegraph[postgres]).- Connection-URL addressing everywhere (
sqlite:///relative,sqlite:////absolute,postgres://...). activegraph migrate --from <url> --to <url>— transaction-per-run, idempotent, one-directional.- Structured JSON logging via
configure_logging(json_output=True)with a documented schema (the operator contract). Metricsprotocol (three methods: counter, histogram, gauge) withNoOpMetricsdefault and referencePrometheusMetricsbackend.runtime.status(recent=N)— frozenRuntimeStatusdataclass for introspection.activegraphCLI:inspect,replay,fork,diff,export-trace,migrate. CLI exit codes documented as contract.- Operator guide at Operating in production.
Migration from v0.7¶
Additive. See
Migration from v0.7 § 3, 7, 8.
Old SQLite path arguments (persist_to="/path/to.db") keep
working; URLs are required for CLI and cross-store operations.
[v0.7] — 2026-05-16¶
The tools and advanced matching milestone.
Added¶
@tooldecorator: tools as first-class primitives with input schema, output schema, determinism flag, cost, timeout.- LLM ↔ tool turn loop owned by the runtime; multi-turn until
the model returns a non-tool response or
max_tool_turnshits. tool.requested/tool.respondedevent pair; replay cache separate from the LLM cache.RecordedToolProvider+RecordingToolProviderfor tests.- Two reference tools:
web_fetch,graph_query(factory-based for graph read access). - Cypher-subset pattern subscriptions via
pattern=on@behavior/@llm_behavior. Compile-time strict; the unsupported tokens raiseUnsupportedPatternErrornaming the offending token. - Negation via
NOT EXISTS { ... }. - Temporal predicates:
activate_after=Nevents (event-count, not wall-clock — keeps replay deterministic). - Tool budgets (
max_tool_calls) + cost-sharing with LLM (max_cost_usdcovers both). - Causal-chain walk crosses tool boundaries via
tool_request_event_idprovenance.
Changed¶
- Prompt assembly normalized — every prompt is content-hashed
via the canonical form; the
prompt_normalized=trueflag appears in the v0.9.1 trace rollup for runs using this format.
Migration from v0.6¶
Additive. v0.6 LLM behaviors continue to work without tools=
declarations.
[v0.6] — 2026-05-16¶
The LLM integration milestone.
Added¶
@llm_behaviordecorator with structured output parsing (Pydantic schema).- Frame-aware prompt construction: system prompt assembled from frame goal + constraints + behavior description + output-schema reminder, in a fixed order.
llm.requested/llm.respondedevent pair with model, full prompt+params, prompt hash, estimated cost, deterministic flag, cache-hit flag.AnthropicProviderreference implementation (readsANTHROPIC_API_KEY; never from code).RecordedLLMProvider+RecordingLLMProviderfor tests (fixtures keyed by SHA-256 of prompt+params canonical form).- Cost accounting: Decimal-precise
max_cost_usdbudget; pre-call estimate viacount_tokens; post-call actual cost from provider'susage. - Structured failure reasons (
llm.network_error,llm.rate_limited,llm.parse_error,llm.schema_violation,llm.fixture_missing,budget.cost_exhausted).
Migration from v0.5¶
Additive. LLM behaviors are opt-in; non-LLM runs unaffected. New
optional dependency activegraph[llm] (anthropic SDK).
[v0.5] — 2026-05-16¶
The resumability milestone. The event log becomes the source of truth.
Added¶
- Full event log persistence via the
EventStoreprotocol; SQLite reference backend with schema version pinned from day one in ametatable. Runtime.load(url, run_id=...)— open, pick a run, replay, return runtime ready to continue.- Strict-replay mode (
replay_strict=True) — re-executes behaviors and firesReplayDivergenceErroron mismatch. - Fork (
runtime.fork(at_event=...)) — new run, copies parent's event log up to the cutoff (inclusive), independent log thereafter. - Structural diff (
parent.diff(other)) — shared / parent-only / fork-only event partitions; divergent objects and relations. - Multiple runs per file; ULID
run_ids; provenance carriesrun_id. - Unfired-event re-queue on load (events emitted but never popped return to the queue on resume).
Migration from v0¶
Additive. v0 in-memory runs continue to work without
persist_to=. New optional dependency
activegraph[sqlite] (stdlib — no extra packages needed).
[v0] — 2026-05-16¶
The core runtime.
Added¶
- In-memory
Graphwith typed objects, typed relations, and an append-only event log. - Function-based (
@behavior) and class-based (subclassBehavior) behaviors. - Relation behaviors (
@relation_behavior) — coordination logic on edges. - Event-type subscriptions with predicate filters (
where=). - Patch system with optimistic concurrency (version-keyed apply;
rejected patches surface as
patch.rejectedevents). - Views with type/depth/recent-events scoping.
- Frames (mission context per run) and policies (per-behavior capability declarations).
- Trace printer (
runtime.print_trace()); causal-chain query (runtime.trace.causal_chain(object_id=...)). - Budgets (
max_events,max_behavior_calls,max_seconds,max_depth, etc.) — runtime stops cleanly when hit; resumable.
Migration from before v0¶
There is no before-v0.
The graph is the world. Behaviors are physics. The trace is the proof.