diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..be291b0
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,52 @@
+# AGENTS.md — orientation for AI agents and coding assistants
+
+This file is for an agent working in this repository. People may read it too; the human
+front door is [README.md](README.md) and the maintainer guide is
+[docs/MAINTAINING.md](docs/MAINTAINING.md).
+
+## What this repository is
+
+The Python package `agentic-system-core` (PyPI): two standalone checkers
+(`validate-wellknown`, `validate-vectors`), a vector runner (`run-vectors`) for seven
+areas, a small reading API, and an `agsc` command that hands every other verb to the Node
+engine. Standard library only. It is not a second implementation of the engine, and it
+must agree with the engine on every case they share. The specification, the vectors and
+the engine live in the [engine repository](https://github.com/andreibesleaga/agentic-system-core).
+
+| Path | What it holds |
+|---|---|
+| `src/agentic_system_core/` | the package; [its README](src/agentic_system_core/README.md) lists every module |
+| `tests/` | the suite; `tests/fixtures/vectors/` are copies of the engine's vectors |
+| `docs/` | [MAINTAINING.md](docs/MAINTAINING.md) (tests, CI, releases) and [ARCHITECTURE.md](docs/ARCHITECTURE.md) (diagrams) |
+| `.github/workflows/` | `test.yml` (every push and pull request) and `release.yml` (a `v*` tag) |
+
+## How to check your work
+
+Run from the repository root, with the engine checked out beside it at
+`../agentic-system-core` and Node 22.13 or later (without them the equivalence tests are
+skipped):
+
+```bash
+python -m venv .venv && .venv/bin/pip install -e ".[test]"
+.venv/bin/python -m pytest
+.venv/bin/pip install ruff==0.16.9 mypy==2.3.1
+.venv/bin/ruff check . && .venv/bin/mypy src/agentic_system_core tests
+```
+
+Tests are deterministic: fixed data, no clock, no network. Write the failing test first.
+
+## What you may change, and what you may not
+
+- **Do not edit** the vector copies under `tests/fixtures/vectors/`; copy them again from
+ the engine. Do not type `src/agentic_system_core/data/spec-index.json`; derive it with
+ the command in [docs/MAINTAINING.md](docs/MAINTAINING.md).
+- **A rule, a vector or the engine is wrong:** propose the change in the engine
+ repository; this package follows in its next release.
+- **Do not add a runtime dependency.** `pyproject.toml` has `dependencies = []` and the
+ package keeps it so.
+- **Keep the README free of relative links:** it is the PyPI page, and a test checks it.
+- **Keep the version in step:** the places that carry it are listed in
+ [docs/MAINTAINING.md](docs/MAINTAINING.md), "Releases".
+- **Never** commit, tag, push or publish on your own; a person does that. A release is a
+ tag the maintainer pushes.
+- Cite rules by id (`AGSC-nn-nn`) and error codes as the specification registers them.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4d6bd65..94efeb5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,8 @@ differently (SemVer's `1.0.0-rc.7` is `1.0.0rc7` here).
## [Unreleased]
+- Docs: README, `docs/MAINTAINING.md` corrected and extended (tests, CI jobs, release steps); `AGENTS.md` and `docs/ARCHITECTURE.md` added.
+
## [1.0.1] - 2026-10-21
A patch of the package. Specification `1.0.0` is unchanged (README, "Version").
diff --git a/README.md b/README.md
index 2b65822..be995be 100644
--- a/README.md
+++ b/README.md
@@ -49,8 +49,9 @@ npm install -g agentic-system-core
the engine's command line: its verbs are exactly the sixteen of the specification
(AGSC-09-07), so `validate-wellknown`, `validate-vectors` and `run-vectors` are not
among them, and it refuses them with `AGSC-E001`. The `agsc` here is a checker
-distribution (the AGSC-09-90 contracts) that runs those three itself and hands
-every other verb to the engine. With both installed, whichever directory comes
+distribution that runs those three itself and hands every other verb to the
+engine. Two of the three, `validate-wellknown` and `validate-vectors`, are AGSC-09-90
+contracts; `run-vectors` is an addition of this package. With both installed, whichever directory comes
first on your `PATH` decides which `agsc` you get; `python -m agentic_system_core.cli`
always reaches this one.
@@ -136,6 +137,10 @@ of it — the rule identifiers, the registered error codes and the declared
version — derived from `spec/` by command; `--spec
` derives the same index
from a live `spec/` directory instead.
+Without `--json`, `validate-vectors --quiet` prints no human-readable output, and
+`--root ` names the folder that the file names in its findings are relative to.
+`run-vectors --level 0|1|2|3` runs only the areas and cases that Level requires.
+
`run-vectors` executes the vectors. **It runs seven areas natively — `build`,
`discovery`, `frontmatter`, `graph`, `jcs`, `links` and `slug` — and names every
other area as not run, with the reason.** Each of the seven is derived from the
@@ -177,7 +182,10 @@ link.fetch(my_fetcher) # nothing fetches by itself
`read_chunks` reads `chunks.jsonl` into a list of records, or returns the shard
manifest as it stands when the file is one. `read_graph` reads `graph.jsonld` as
plain JSON — no RDF library, no triples; turning it into a graph is a job for a
-library you choose.
+library you choose. Both take the bytes or text you already hold; `read_chunks_path`
+and `read_graph_path` take a file path instead. `link.fetch(fetcher)` calls your
+callable with the link's `href` alone, not with the three arguments of the fetcher
+that `validate` takes.
A member the reader does not know — in a chunk line, a chunk manifest, the graph or
a discovery document of a later version of the specification — is neither refused
@@ -241,8 +249,13 @@ python -m venv .venv && .venv/bin/pip install -e ".[test]"
.venv/bin/python -m coverage run -m pytest && .venv/bin/python -m coverage report
```
-Deterministic, fixed data, no clock, no network. The only subprocess in the
-suite is the Node tool in the equivalence test.
+Deterministic, fixed data, no clock, no network. The subprocesses are the Node tool
+and vector runner in the two equivalence tests, and the workflow steps that
+`tests/test_ci_workflows.py` runs on scratch input. The tests that need Node and the
+engine checkout beside this repository (`../agentic-system-core`) are skipped, with a
+message, when either is absent: a fresh clone passes with those skipped, and a pull
+request is checked with the engine present
+([docs/MAINTAINING.md](https://github.com/andreibesleaga/agentic-system-core-python/blob/main/docs/MAINTAINING.md)).
## Licence
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
new file mode 100644
index 0000000..10ecfcf
--- /dev/null
+++ b/docs/ARCHITECTURE.md
@@ -0,0 +1,94 @@
+# Architecture of the Python package
+
+**Summary.** What the package talks to, and how its modules call each other. The
+diagrams use the module names of `src/agentic_system_core/`; the one-line purpose of each
+module is in [src/agentic_system_core/README.md](../src/agentic_system_core/README.md).
+
+**Read after:** the [README](../README.md). **Read next:** [MAINTAINING.md](MAINTAINING.md).
+
+## The package and its surroundings
+
+The package reads a discovery document and a vector set, writes a diagnostics envelope
+and an exit code, and hands every verb it does not run itself to the Node engine. It opens
+a network connection only when `--allow-network` is on the command line.
+
+```mermaid
+flowchart LR
+ user["Person or CI job"] -->|"agsc verb, or Python import"| pkg["agentic-system-core
(this package)"]
+ pkg -->|"reads"| doc["Discovery document
(file, or URL with --allow-network)"]
+ pkg -->|"reads"| vec["Conformance-vector set
(engine repo or npm package)"]
+ pkg -->|"diagnostics envelope, exit code"| user
+ pkg -->|"the sixteen engine verbs, as typed"| node["Node engine
(npm agentic-system-core, agsc)"]
+```
+
+The engine is a separate program: the package never imports it. The tests run it beside
+this repository to prove the two agree (see [MAINTAINING.md](MAINTAINING.md)).
+
+## Modules and calls
+
+An arrow means "imports". `cli.py` is the only entry point; the rest is a library.
+`jcs.py`, `diagnostics.py` and `urls.py` are used from several places and are left out of
+the picture to keep it readable: `jcs.py` (canonical JSON) is imported by many
+modules, `diagnostics.py` (the envelope and the exit code) by `cli.py`, `wellknown.py`
+and `vectors.py`, and `urls.py` by `net.py`, `reader.py` and `wellknown.py`.
+
+```mermaid
+flowchart TD
+ cli["cli.py
agsc command"] --> wellknown["wellknown.py
discovery-file checker"]
+ cli --> vectors["vectors.py
vector checker and runner"]
+ cli -->|"passes fetch in"| net
+ cli -->|"subprocess, no shell"| node["Node engine agsc"]
+ wellknown --> net["net.py
guarded HTTP reader"]
+ vectors --> areas["areas/
one handler per area"]
+ vectors --> index[("data/spec-index.json")]
+
+ areas --> frontmatter["frontmatter.py"]
+ areas --> slug["slug.py"]
+ areas --> links["links.py"]
+ areas --> graphmod["graph.py"]
+ areas --> context["context.py"]
+ areas --> discovery["discovery.py"]
+ areas --> site["site.py"]
+ areas --> wellknown
+ areas --> rdf
+
+ frontmatter --> yamlsubset["yamlsubset.py"]
+ frontmatter --> slug
+ frontmatter --> links
+ links --> markdown["markdown.py"]
+ graphmod --> links
+ graphmod --> rdf["rdf.py"]
+ context --> rdf
+ discovery --> graphmod
+ discovery --> links
+
+ reader["reader.py
reading API"]
+```
+
+`reader.py` stands apart: no other module imports it, and it imports only the package
+constants, `urls.py` and `jcs.py`. It never opens a connection; a fetcher is passed in by
+the caller.
+
+## One run of `agsc validate-wellknown`
+
+```mermaid
+sequenceDiagram
+ participant U as Person or CI job
+ participant C as cli.py
+ participant W as wellknown.py
+ participant N as net.py
+ participant D as diagnostics.py
+ U->>C: agsc validate-wellknown FILE, level 2
+ C->>W: validate(target, level, peer, dev, allow_network, fetcher=net.fetch)
+ W->>W: load(target): read the file
+ opt target is a URL and network reads are allowed
+ W->>N: fetch(url): classify, pin, follow at most three redirects
+ N-->>W: final URL, headers, bytes
+ end
+ W->>W: check(source, level, findings)
+ W->>D: envelope(findings, verb)
+ D-->>W: envelope
+ W-->>C: envelope, number of files read
+ C->>D: exit_code(envelope)
+ C-->>U: report, exit code 0, 1 or 2
+```
diff --git a/docs/MAINTAINING.md b/docs/MAINTAINING.md
index b9f8e7f..d9f18a5 100644
--- a/docs/MAINTAINING.md
+++ b/docs/MAINTAINING.md
@@ -15,15 +15,20 @@ Native, in the standard library: `agsc validate-wellknown`, `agsc validate-vecto
`links`, `slug`) and the reading API. Forwarded, exactly as typed and without a shell:
the sixteen engine verbs, to the npm package's `agsc` when it is on the `PATH`. The
README's "What runs here" and "What forwards to Node" sections are the detail.
+[ARCHITECTURE.md](ARCHITECTURE.md) draws the parts and how they call each other.
## The equivalence proof
`tests/test_equivalence.py` runs this package's discovery-file checker and the engine's
`tools/validate-wellknown` over the same documents, at every Level, with and without a
peer, and compares the status, the counts and each finding's position and code. The
-cross-runner test runs the engine's vector runner over its live set and requires both
-runners to agree on every vector of the seven areas. Both need the engine checked out
-beside this repository at `../agentic-system-core` and Node 22.13 or later.
+cross-runner test (`tests/test_vector_equivalence.py`) runs the engine's vector runner
+over its live set and requires both runners to agree on every vector of the seven areas.
+Both need the engine checked out beside this repository at `../agentic-system-core` and
+Node 22.13 or later; without them they are skipped with a message. The tests that read
+the built discovery files of the two sites use the checkouts beside this repository, at
+`../AgenticSystemCore.com` and `../AgenticSystemCore-Patterns`, and skip a site that is
+absent.
## Tests
@@ -32,6 +37,26 @@ python -m venv .venv && .venv/bin/pip install -e ".[test]"
.venv/bin/python -m pytest # pyproject already passes -q; do not add another
```
+The static checks, `ruff` and `mypy`, are installed and run as
+[CONTRIBUTING.md](../CONTRIBUTING.md) shows.
+
+## Continuous integration
+
+`.github/workflows/test.yml` runs on every push and pull request, by hand
+(`workflow_dispatch`) and as the test stage of a release; its header comment is the full
+description. In short:
+
+- `tests (Python 3.9)`, `tests (Python 3.13)` and `tests (Python 3.14)` run `pytest`
+ under `coverage` (the report fails below the floor in `pyproject.toml`) with the engine
+ and the main site checked out beside this repository, then the engine's persona-l and
+ persona-o scenarios, and fail if either was skipped.
+- `ruff and mypy (Python 3.13)` runs `ruff check --select E4,E7,E9,F src tests` and
+ `mypy src/agentic_system_core tests` at exact pins.
+- The engine is taken at `ENGINE_REF` (`main`) unless a manual run names another ref.
+- The required checks on `main` are the 3.9 and 3.13 test legs and the ruff and mypy job;
+ the 3.14 leg is not required, but a release waits for it.
+- `.github/dependabot.yml` opens one monthly pull request for the pinned actions.
+
## Refreshing what is copied from the specification
The rule index (`src/agentic_system_core/data/spec-index.json`) is derived, never
@@ -51,10 +76,25 @@ A release is a tag `v` (PEP 440 spelling, for example `v1.0.0`) pushed
the maintainer. `.github/workflows/release.yml` checks that the tag is the version and
that the tagged commit is on `main`, runs the whole test lane (`test.yml`, the same
legs, scenarios and static checks as a pull request) with the engine checked out at its
-package tag of the same version (`1.0.0rc7` tests against `v1.0.0-rc.7`, `1.0.0` and
-`1.0.0.post1` against `v1.0.0`, `1.0.1` against `v1.0.1`), builds once, and publishes
-through PyPI trusted publishing: no token is stored in the repository, and every action
-is pinned to a commit. The engine's package tag `v` must therefore exist
+package tag of the same version (a release candidate `X.Y.ZrcN` tests against `vX.Y.Z-rc.N`,
+`1.0.0` and `1.0.0.post1` against `v1.0.0`, `1.0.1` against `v1.0.1`), builds once, and
+publishes through PyPI trusted publishing: no token is stored in the repository, and
+every action is pinned to a commit. The publish job runs in the GitHub environment
+`pypi`, which has required reviewers, so the upload waits for an approval. The engine's package tag `v` must therefore exist
before this package's tag is pushed; it exists for every engine release, while a
specification tag exists only when the specification moved. The version in `pyproject.toml` must be the engine's version in PEP 440
spelling; the engine's `tools/release` checks that the two agree.
+
+Before tagging, write the new version in every place that carries it: `pyproject.toml`,
+`__version__` and `SEMVER_VERSION` in `src/agentic_system_core/__init__.py`, the
+"Version" section of the README, and a heading in `CHANGELOG.md`. `tests/test_cli.py`
+also names the version and fails until it is updated. To check the build before tagging,
+run what the release lane's build job runs:
+
+```bash
+.venv/bin/pip install build==1.6.1 twine==7.0.0
+.venv/bin/python -m build
+.venv/bin/python -m twine check --strict dist/*
+```
+
+`build/` and `dist/` are ignored by git.
diff --git a/docs/README.md b/docs/README.md
index 8e39349..2a57919 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -7,4 +7,5 @@ the package wants the repository [README](../README.md) instead.
| File | What it holds |
|---|---|
-| `MAINTAINING.md` | how the package is arranged, what runs here and what is handed to the Node engine, the equivalence proof, the tests, refreshing what is copied from the specification, and releases |
+| `MAINTAINING.md` | how the package is arranged, what runs here and what is handed to the Node engine, the equivalence proof, the tests, the CI jobs, refreshing what is copied from the specification, and releases |
+| `ARCHITECTURE.md` | what the package talks to, how its modules import each other, and one run of `validate-wellknown`, as Mermaid diagrams |