nlght-ai v0.1.1
Change notes — v0.1.1
Released: 2026-07-19
-
release: process the final public-mirror exclude entry even when the file
has no trailing newline, preventing paths such asscripts/releasefrom
leaking into the public snapshot. (slice:
working/public-mirror-exclude-final-line) -
roadmap: document the planned move from blocking tool loops to asynchronous
orchestration where reasoning can continue around in-flight calls, suspend or
wait when necessary, and supervise subprocesses up to complete child
workflows. (slice: working/parallel-tool-orchestration-roadmap) -
agent skill: expand the
nlghtproject skill with code-backed public APIs,
complete platform/provider options, workflow/tool/playbook/session/protocol
patterns, diagnostics, deployment, and E2E coverage guidance; correct the
mutable step context, signal, and tool-event examples. (slice:
working/nlght-skill-reference) -
fix(coverage): count regular cloud provider implementations now that their
e2e suite contributes to CI coverage; exclude instrumented*_debug.py
duplicates and retain theollama_local.pyexclusion because its daemon-
backed e2e suite intentionally remains local-only. (slice:
working/provider-coverage) -
fix(ci): make
https://api.openai.com/v1the regular/debug OpenAI provider
default so an explicitly empty CIOPENAI_BASE_URLcannot produce relative
request URLs; retain non-empty Azure/proxy overrides. Run architecture tests
without starting an empty coverage process and generate the combined report
and XML in a dedicated step after unit, integration, and cloud e2e coverage
have been combined. (slice: working/openai-default-endpoint; ADR-0030) -
docs(skill): validate the nlght project skill metadata and synchronize its
model-provider guidance with the current provider kinds, defaults, debug
clients, continuation requirements, and shared e2e conventions. (slice:
working/update-nlght-skill) -
fix(model): the non-streaming Ollama tool loop (
call()with a catalog) never
sent the tool schemas — bothollama_localandollama_cloudbuilt the
/v1/chat/completionsbody as{model, messages, stream:False}with no
tools, so the model was never told the tools existed and returned no tool
call (streaming already sent them). Proven by an evidence log
(tools_in_request=None); fixed by deriving OpenAI-format tools from the
catalog and adding them to the body, mirroringstream(). Verified: the local
Ollama e2e suite now passes 5/5. (slice: working/model-provider-e2e) -
chore(model): preserve the fully instrumented Anthropic, OpenAI, Google,
Ollama local/cloud clients and shared Ollama HTTP transport as*_debug
modules, with both Ollama debug clients isolated onto_ollama_http_debug.
Regular production clients no longer emit diagnostic INFO/DEBUG request,
response, token, chunk, or tool-loop traces; operational warnings and all
provider bug fixes remain unchanged. (slice: working/model-provider-e2e) -
fix(model): Anthropic's streaming tool loop never invoked tools.
AsyncMessageStream.get_final_message()is async but was called without
await, sofinalwas a coroutine, theexceptswallowed the resulting
AttributeError, and tool_use blocks were never surfaced as tool_call events.
Non-streaming worked; both streaming tool paths (native catalog loop and the
step-drivenstream()loop) silently produced no call. Added theawait; a
regression test asserts a tool_use block in the final message reaches the
caller as a tool_call event. (slice: working/model-provider-e2e) -
fix(model): Google/Gemini 3.x tool loops failed with
400 INVALID_ARGUMENT
("Function call is missing a thought_signature"). Gemini 3.x returns a
thought_signatureon function_call parts and requires it echoed back on the
continuation; the client dropped it by rebuilding the part. The native loops
(call()andstream()with a catalog) now re-append the model's own turn /
function_call Parts verbatim instead of rebuilding them, preserving the
signature. The step-driven path carries it (base64) through the canonical
tool-turn (append_ollama_native_tool_turn) and_to_google_contentsdecodes
it back onto the Google part; other providers ignore the extra key. Unit tests
cover the canonical round-trip; confirmed against the live Gemini API (all
Google tool-loop e2e scenarios pass). (slice: working/model-provider-e2e) -
test(model-clients): restructure the e2e model-client suite so every client
is exercised by one shared scenario set (tests/model_client_suite.py:
non-stream call, streaming tokens, native tool loop in call() and stream(),
and the step-driven append_tool_turn() loop). Cloud clients (Anthropic,
OpenAI, Google, Ollama Cloud) stay intests/e2e; the local Ollama client
moves to a newtests/local_e2epackage that runs the same suite and is
deselected by default (-m "not local_e2e") — it needs a live daemon and
never runs in CI. Dropped the misnamed "ollama proxy" tests and the
local-Ollama e2e conftest. Env naming is now uniform and prefix-free
(<PROVIDER>_API_KEY+<PROVIDER>_MODEL, plusOPENAI_BASE_URL); the old
NLGHT_E2E_*model vars and the…_OLLAMA_CLOUD_URL/KEY/MODELtrio are gone.
Ollama Cloud gates onOLLAMA_API_KEYalone (default modelgpt-oss:20b, a
free-tier/no-subscription model), needs no URL. Also fixed a real suite bug: thebackendfixture was
module-scoped, so async SDK clients outlived their event loop and the Google
client raised "Event loop is closed" from the second test on — now
function-scoped, one client per test. README gains an e2e-scope note under the
badge block: the local Ollama suite is intentionally not run in CI (no daemon)
and is verified locally. (slice: working/model-provider-e2e) -
feat(model): Ollama Cloud
base_urldefaults tohttps://ollama.com
(overridable) so a cloud provider needs only an API key.OllamaCloudClient
gains the default;CompositionServicepicks the endpoint per kind
(ollama-local→ localhost, cloud kinds → ollama.com), any configured
base_urlstill wins. (slice: working/model-provider-e2e; ADR-0029) -
fix(model): Google's
gemini-2.0-flashwas decommissioned by the
provider (HTTP 404, "no longer available"), breaking all 5 Google e2e
scenarios. Replaced the hardcoded default everywhere it appeared
(production wiring incomposition_service.py, e2e test default, both CI
workflow env fallbacks, both doc examples) withgemini-3.5-flash.
Separately,GoogleModelClient.list_models()previously only ever
returned the static_CONTEXT_WINDOWStable — it now queries the live
Google Generative AI API first (client.aio.models.list()), falling back
to the static table only on error, matching the pattern
OpenAICloudModelClient.list_models()already used. Token-budget
resolution is unchanged (still the static prefix-matched table — Google
exposes no reliable context-window metadata endpoint). New/updated unit
tests mirror OpenAI's existing live/fallback test pair.
(slice: working/google-e2e-live-model-standards) -
fix(badge): replace the live
img.shields.io/pypi/v/nlght-aiPyPI badge
with a self-generated staticpypi.svg, rendered the same way as
tests.svg/coverage.svg(anybadge). Owned by a third lifecycle:
prepare_release.pyregenerates it from the version being bumped, at
bump-before-tag time (ADR-0025), so it travels with the normal pre-tag
commit — no live PyPI query, no new release-workflow step (the tagged
commit already carries the right file viagit archive). Initial value on
main:v0.0.1.release_badges.pygainedgenerate_pypi()and apypi
CLI subcommand;pypi.svgjoined theBADGEStuple that drives both the
repo's own README and the tag-pinned public/PyPI-description markdown.
(slice: working/pypi-self-generated-badge, ADR-0028) -
docs(memory): explain the full store-to-prompt pipeline, including Weighted
Sigmoid as the default relevance strategy, Linear and Multiplicative
alternatives, effective thresholds and limits, cache semantics, prompt
disclosure controls, and the boundary between Python wiring, YAML, and fixed
internals. (slice: working/session-doc-illustrations) -
docs(models): document context-budget resolution and unknown-model fallbacks
for every provider; distinguish local Ollama/api/showdiscovery from
static cloud tables, and correct the shared partition chart to the actual
Store/Prompt/Response/Safety 40/20/20/20 split.
(slice: working/model-provider-budget-docs) -
docs(access): visualize rule-based access-policy evaluation, including the
default-open no-rule path, priority/tie ordering, and fail-closed allowlist
semantics when subject rules exist. (slice: working/echarts-visualizations) -
docs(concepts,hero): keep Concepts graph nodes and legends inside their
chart bounds across desktop and narrow layouts, and add a responsive index
hero topology that connects an app through nlght-ai to its four core user
benefits: explicit workflows, persistent memory, any model, and self-hosting.
(slice: working/echarts-visualizations) -
docs(diagrams): migrate every fixed rectangular node-link chart to the
responsive rectangle-edge integration, preserving native labels, tooltips,
emphasis, and edge semantics while making arrow endpoints exact; improve
Signals Sankey labels and the final responsive hero composition.
(slice: working/rect-graph-doc-migration) -
docs(hero): add three temporary hero-visual preview pages
(/hero-variant-1 platform map with request particles, /hero-variant-2
draggable concept constellation, /hero-variant-3 timeline pulse) for the
index-page hero decision; the chosen one moves into docs/index.mdx and
the previews get deleted. (slice: working/echarts-visualizations) -
docs(concepts): add in-chart legends to all five concept diagrams (graph
categories for node classes, styled legend items for line meanings), plus
caption lines and layout fixes for narrow-column rendering.
(slice: working/echarts-visualizations) -
docs(architecture): replace the request-flow mermaid sequence diagram with
an interactive matrix+graph sequence (numbered rows, per-message payload
tooltips, legend-toggled stream variants, call/return legend) and add a
chord diagram of the allowed layer import directions.
(slice: working/echarts-visualizations) -
docs(deployment,workflows): replace the last two mermaid diagrams — the
migrate flow becomes an interactive graph with config-key tooltips, the
workflow DB schema becomes a collapsible instance tree with step flags.
The docs are now mermaid-free. (slice: working/echarts-visualizations) -
docs(configuration,roadmap,architecture): add new visualizations — context
budget stacked bar (model-providers), atom-promotion sankey
(session-memory), open-items-to-stable bar (roadmap), and a zoomable
source-layout treemap (source-structure).
(slice: working/echarts-visualizations) -
docs(concepts): replace the workflow mermaid diagram with an interactive
ECharts step graph (realistic five-step example, verdict-labelled edges,
hover adjacency focus, per-node DB-row tooltips) rendered via the landing
<EChart>component. (slice: working/echarts-visualizations) -
docs(concepts): fix graph edge labelling — disable series-level default
edge labels (they rendered as "source > target" on unlabelled links),
keep explicit labels only on horizontal edges, move loop/vertical edge
wording into tooltips, and widen the prompt-flow layout.
(slice: working/echarts-visualizations) -
docs(concepts): convert the remaining concepts diagrams to interactive
ECharts — prompt-assembly flow and executor loop as fixed-layout graphs,
session resolution as a collapsible tree carrying the four store questions,
signal flow as a two-path sankey; removes the legacy hand-crafted executor
SVG. (slice: working/echarts-visualizations) -
fix(licensing): tolerate up to five minutes of positive clock skew on signed
machine-lease issuance and log reason-specific, secret-free temporal
validation details while keeping expiry, TTL, signature, and binding strict. -
fix(badges): show
success/skip/totalfor every test family and label the
count order, so skipped E2E suites are distinguishable from failures. -
docs(install): standardize README, Quickstart, deployment, configuration,
tool, and project-skill examples on sync-safe uv installation and command
execution; document the separateuv tool installpath for CLI-only use. -
fix(badges): render all test categories as adjacent coloured segments in one
tests.svg, giving the family one shared outer shape in development and
release READMEs. (slice: working/connected-test-badge; refs: ADR-0027) -
ci(badges): decouple development and release badge lifecycles. Normal CI now
updates only this repository's SVGs while its README keeps relative links;
release CI independently generates tagged results and injects public URLs
pinned to the immutable release tag into package metadata and the public
export. (slice: working/decouple-dev-release-badges; refs: ADR-0027) -
build(release): complete PyPI metadata with a summary, keywords, Python and
typing classifiers, direct Issues/Releases URLs, and absolute public badge
and documentation links suitable for the PyPI long description. After PyPI
and public mirror/tag publication, the workflow now creates the public GitHub
Release from the committed versioned change note and marks RCs as
prereleases. (slice: working/public-release-metadata; refs: ADR-0025) -
ci(badges): split the aggregate test badge into a compact unit,
architecture, integration, and E2E family that reports executed/total per
category. CI and releases now run the environment-gated E2E suite with the
provider SDK extras installed; configured providers execute while missing
environments remain visible as skipped tests. (slice:
working/test-badge-family; refs: ADR-0025) -
docs(release): define
0.1.0as internal-only and0.2.0as the first
public Developer Preview, align current roadmap/readiness references, and
add the end-to-end maintainer runbook for RC and final PyPI delivery.
(slice: working/release-0.2.0-runbook) -
fix(admin): protect every mutating Admin UI request with a constant-time
checked CSRF cookie/request token, change version forking from GET to POST,
and serve pinned Pico CSS, htmx, and Cytoscape assets from the package
instead of third-party CDNs. (slice: working/security-admin-hardening;
findings: SEC-002, SEC-003) -
docs(security): add a German decision report that triages the attached
Bandit, Semgrep, and pip-audit results into actionable risks, false
positives, release recommendations, and explicit owner decisions. (slice:
working/security-findings-report) -
docs(adr): standardize every decision-record heading on the
ADR-NNNN — English Titleformat, translate ADR-0010 through ADR-0015 into
English, and clarify Admin UI startup with separate Unix/macOS and
PowerShell commands plus the complete browser URL. (slice:
working/standardize-adrs-admin-ui) -
test(runtime): raise the executable aggregate line-coverage baseline from
90.22% to 92.19% with 22 behavioural tests for workflow concurrency,
coordinator/runtime cleanup, Docker lifecycle and SDK failures, robots and
HTML fetch handling, and structured HTTP probes. Unexpected step exceptions
now correctly record failed request metering instead of success. (slice:
working/coverage-runtime-and-workflow) -
test(coverage): raise the executable aggregate line-coverage baseline from
77.13% to 90.22% with 26 behavioural tests for Hive Mind extraction,
ingestion, attachment/artifact handling, coordinator lifecycle and
persistence, plus the memory-artifact tool. Provider adapters that require
unavailable external API access are explicitly omitted from the baseline
report until their separate suite is enabled; shared model HTTP, chunking,
and tool logic remains measured. (slice: working/coverage-over-90) -
feat(ops): add
GET /readyzalongside/healthz— reports database
readiness (a lightweightSELECT 1viaContainer.engine, 503 when
unreachable) instead of only process liveness. Model providers are
deliberately not pinged (see ADR-0026). Unit-tested (no DB configured /
reachable / unreachable). (slice: working/first-release-readiness;
finding: RLS-013; refs: ADR-0026) -
ci(security): add baseline, non-blocking dependency-vulnerability and SAST
scanning totests.yml— pip-audit, Bandit, and Semgrep, all
continue-on-errorand uploaded as asecurity-scan-resultsartifact
(SARIF for Bandit/Semgrep, JSON for pip-audit);results/coverage.xmland
the junit files are uploaded separately astest-results. Non-blocking
because the codebase has never been scanned before — baseline: 25 Bandit
findings (23 low, 2 high), 0 pip-audit CVEs; enforcement policy is a
follow-up decision after triage.bandit[sarif]andpip-auditjoin the
devdependency group;semgrepis installed ad hoc in CI only (no
native Windows wheel, would break localuv syncon this repo's Windows
dev environment). (slice: working/first-release-readiness; finding:
RLS-013; refs: ADR-0026) -
ci(release): switch to a bump-before-tag release flow (ADR-0025, amends
ADR-0021) — the version bump and change-note rotation now happen as one
ordinary commit onmainbefore the tag is created (via the new
scripts/release/prepare_release.py), so the tag, wheel metadata, public
mirror tag, and release notes all identify the exact same commit; the
release job no longer patchespyproject.tomlin-CI or commits back to
mainafter publish. The release job also now runs the same gate set as
tests.yml(ruff, mypy, unit, integration, architecture) plus a new
install-smoke test against the actually-built wheel in a fresh venv
(import, CLI dispatch, packaged assets, offline migration SQL,
create_app()construction) before publishing.set_version.pygains a
--checkmode used by the release job to fail fast if a tag doesn't match
the already-committed version. (slice: working/first-release-readiness;
findings: RLS-004, RLS-005, RLS-012, user-cleared) -
ci(tests):
tests.ymlnow also generates tests/coverage badges — not only
the release workflow's public-mirror snapshot. Coverage accumulates across
the three existing unit/integration/architecture steps (--cov-append);
on every push tomain(not PRs) the badges are committed to
.github/badges/and re-injected into the repo's ownREADME.mdmarker
block, with[skip ci]to avoid a self-trigger loop.
scripts/release/release_badges.py'sgeneratenow sums across multiple
junit files (tests.yml runs three separate suites; release.yml runs one
combined suite) and gains acoveragesubcommand that renders the
coverage badge fromcoverage.py's XML report viaanybadge— replacing
thecoverage-badgepackage, which importspkg_resourcesand no longer
works against current setuptools.inject()now keeps the marker
comments in place instead of consuming them, since it re-runs on every
push rather than once at release time.anybadgemoves into thedev
dependency group so its existing unit test runs instead of skipping in
normal PR CI. (slice: working/first-release-readiness) -
docs(quickstart): add an explicit "Start PostgreSQL" step (
docker run postgres) to Quickstart (docs site + README), between configuring
platform.yamland runningnlght-ai migrate— previously the guide
assumed a running, reachable PostgreSQL instance without saying how to get
one. (slice: working/first-release-readiness) -
fix(security):
serve()logs an explicit WARNING when the Admin UI is
enabled (NLGHT_ADMIN) on a non-loopback binding — the UI has no
authentication, so public bindings need an authenticating proxy. Closes
the last open public-Alpha criterion of RLS-009.
(slice: working/first-release-readiness; finding: RLS-009, user-cleared) -
ci(tests): integration and architecture suites are merge gates — the PR/
push workflow runs them after the unit suite instead of only at release.
(slice: working/first-release-readiness; finding: RLS-011, user-cleared) -
fix(packaging): ship the PEP 561
py.typedmarker in the wheel so
downstream Mypy/Pyright consume the inline types; presence is unit-tested.
(slice: working/first-release-readiness; finding: RLS-010, user-cleared) -
docs(diagrams): second prototype for the mermaid replacement — the
workflow executor flow in concepts.mdx is a hand-crafted inline SVG in
the docs-site visual language (theme CSS variables, monospace, teal
accent; dark/light automatic). Root cause of the first attempt's broken
text positions fixed: Astro-MDX drops camelCase SVG attributes, so
text-anchor/dominant-baselinenever reached the DOM — now kebab-case
with centered baselines, verified against the built site HTML.
Remaining diagrams follow after look-and-feel sign-off.
(slice: working/concepts-diagrams) -
feat(licensing): pin the trusted Keygen origin/account/Ed25519 key and add
Keygen-issued 72-hour machine leases for bounded offline startup; verify
signature, algorithm, TTL, embedded license state, and fingerprint before
fallback, while explicit invalidity remains fail-closed. (slice:
working/rls-008-signed-machine-lease; finding: RLS-008; refs: ADR-0024) -
docs(contracts): synchronize README and canonical skill with shipped provider
kinds, the external demo repository, SessionResultStore safety semantics,
optional LLM handling, and the complete built-in tool set. (slice:
working/rls-007-public-contracts; finding: RLS-007) -
refactor(knowledge): remove the unfinished Knowledge Store prototype from the
0.1.0core platform, including its premature ORM tables, port, adapter,
package extra, and tests; retain the pre-removal state on
feature/knowledge-storeand keep the cohesive pipeline as planned future
work under ADR-0023. (slice: working/rls-003-remove-knowledge; finding:
RLS-003; refs: ADR-0023) -
fix(security): restore certificate verification for all license validation,
activation, and entitlement requests by removing the globalverify=False
client override; add regression coverage for secure client construction.
(slice: working/rls-001-license-tls; finding: RLS-001) -
fix(security): remove the unused raw
/configdiagnostics endpoint entirely;
it now returns 404 in default and Admin-enabled modes. Document unauthenticated
Admin UI as an explicitly accepted0.1.0Developer Preview limitation and
retain authentication plus deployment guardrails on the stabilization
roadmap. (slice: working/rls-002-remove-config; finding: RLS-002) -
docs(review): record the first-release readiness assessment with three P0
blockers (licensing TLS verification, unauthenticated raw config exposure,
and ORM/Alembic schema drift), public-Alpha gates, verified quality baseline,
remediation order, and checkable exit criteria. (slice:
working/first-release-readiness) -
docs(architecture): expand the Structurizr workspace from three broad views
to seven focused views: System Context, Containers, Extension Points,
Workflow Runtime components, workflow-request and tool-calling dynamics,
and the Single Node deployment topology. The renderedworkspace.jsonis
synchronized. (slice: working/architecture-views-and-svg) -
docs(architecture): audit workspace.dsl against the code and fix drift —
Playbook Catalog is implemented (YAML loader + builder), no longer
"[planned]", and does not persist to the DB; removed three false
persistence relationships (workflow runtime, workspace management, and
session memory do not use the PostgreSQL persistence service — session
memory persists via its own FileSystemBackend); Payload Detection is
explicitly marked PLANNED (no code exists); the built-in tool list and
the model-provider description now match reality (provider-specific
adapters, full tool set); new Admin UI container with operator and
persistence relationships. Rendered workspace.json still needs a
re-export via Structurizr. (slice: working/docs-content-pass) -
refactor(typing): enforce
ANN401(no bareAnyin signatures) and
PLC0415(imports at file top) via ruff. All 76Anysites in src+tests
replaced with precise types orobject; 5 dynamic construction seams
(tool/store constructors forwarding into typed keyword surfaces) carry a
reasoned# noqa: ANN401. Of 59 function-level imports in src, 13 were
plain hoists (pathlib/sys/ast/model imports moved to file top — incl.
removing two runtime imports from hot paths in the hive-mind
coordinators); the remaining optional-extra/lazy seams carry a reasoned
# noqa: PLC0415. Tests keep function-level imports
(per-file-ignore) — idiomatic for importorskip-gated modules. Side
effects: admin_parse_json_fieldnow rejects non-object JSON with 422
(a JSON array can no longer land in a dict config column), and
ToolContract.executeis typedobject-in/object-out with the result
coerced via an isinstance check instead ofhasattr.
(slice: working/docs-content-pass) -
refactor(bootstrap): move
fastapi_app.py(create_app, lifespan, route
registration) fromadapters/inbound/http/intobootstrap/— it is
the composition entry (builds the container) and violated the layering
as an adapter. Theadapters must not import bootstrappytestarch rule
is now enforced (15 rules total). uvicorn targetnlght.main:create_app
unchanged; architecture docs updated. (slice: working/docs-content-pass) -
test(architecture): complete the clean-architecture layering matrix in
tests/architecture — new pytestarch rules: ports must not import
application, adapters must not import application, application must not
import bootstrap, extensions must not import application (14 rules
total). Findings recorded for decision: core→ports imports are all
TYPE_CHECKING-only (accepted pattern for now);
adapters/inbound/http/fastapi_app.py imports bootstrap at runtime — a
real layering violation awaiting a move-or-except decision before an
adapters↛bootstrap rule can be added.
(slice: working/docs-content-pass) -
test(e2e): live-API e2e tests for all four cloud model providers
(Anthropic, OpenAI, Google, Ollama Cloud) — per provider: non-streaming
call, streaming tokens, the native tool loop in both call() and
stream() form, and the step-driven tool loop via append_tool_turn(); a
real tool must be executed and its result must reach the final answer.
Gated on per-provider env keys (skip without secrets); the local-Ollama
reachability skip no longer gates the whole e2e package.
(slice: working/model-provider-e2e) -
ci(release): static tests/coverage badges for the public snapshot. The
release workflow now runs unit+integration+architecture in one combined
pytest run with--cov=nlght+ junit output, renders
coverage.svg(coverage-badge) andtests.svg(anybadge via the new
testablescripts/release/release_badges.py), copies both into the
public mirror at.github/badges/, and replaces the README marker
block (<!-- release-badges:start/end -->) with local badge references
plus a PyPI badge during mirroring. The dev README carries only the
empty marker block; the previous public-repo Actions badge was removed
(it reflected the wrong repo's CI). Unit tests cover junit
summarization, README injection, and SVG generation.
(slice: working/release-badges) -
docs(roadmap): the SystemPromptBuilder Overhaul now describes
store-driven prompt composition as its first planned improvement — the
four session stores (Directive/Conversation/SessionResult/WorkingMemory)
will feed defined prompt sections per scope; today this needs manual
step code. session-memory.mdx points to that roadmap entry from "The
four stores". (decision: roadmap description instead of implementing
the wiring pre-release; slice: working/hive-mind-prompt-wiring) -
docs: document the Resource concept and the Admin UI (both previously
missing). concepts.mdx gains a "Resource" section (resource row =
activation of a registered implementation; restrictions live in access
policies, not on the row) plus a "where each concept lives" mapping of
concepts to their data structures (DB tables / playbook YAML /
platform.yaml / code registries). New user-docs page
deployment/admin-ui (enabling via NLGHT_ADMIN=1, workflows/versions/
graph editor, resources, access policies, first-time setup; explicit
no-auth warning) + sidebar entry. Tools pages and the Docker Runtime
page now state that tools need resource rows and link Admin UI +
access policies. docker.mdx configuration corrected to the real
singularos_runtimeblock (the documentedruntime_subsystems
variant and the "both runtimes in parallel" section did not match the
parser) incl.workspace_path/extra_hostskeys.
(slice: working/resources-admin-ui-docs) -
docs(config): sync all configuration docs with what the code actually
parses. Real drift fixed: the HTTP gateway kind ishttp— docs and
examples everywhere saidkind: http-gateway, which only worked because
the 0.0.0.0:8000 fallback matched the example; the stale
runtime_subsystems:schema in configuration-model.mdx,
database-migrations.mdx, and README replaced by the realgateways/
os_runtime/integrations.persistence.workflowslayout; the three
Ollama provider kinds documented (ollama-localfor local serve,
ollama/ollama-cloudfor the cloud client) incl. previously
undocumentedapi_key/headers/timeout config keys. Quickstart (docs +
README) gains an explicit "Initialize the database" step (nlght-ai migrate) before first start. Docker Runtime graduates out of
Experimental (badges removed on its page and in the platform.yaml
reference; roadmap section removed) — validated by 12h+ long-running
production tasks; the DEV-only security caution now scopes to
os_runtime: kind: localspecifically. session-memory.mdx no longer
mentions license tiers. nlght skill updated to match (gateway kind,
ollama kinds). (slice: working/docs-content-pass) -
docs(sidebar): flatten Capability Management (Built-in Tools, Custom
Tools, Playbooks as direct items — the playbooks page is labeled
"Playbooks" instead of "YAML Format") and move Access Policies into a new
top-level "Authorization" group. No slugs changed.
(slice: working/sidebar-authorization-group) -
test: enforce the repository copyright header on every Python file under
tests/via a new guard test (test_copyright_headers.py); added the
header to all 95 test files that lacked it.src/already carried the
header throughout. -
feat(access-policy): replace the
allowed_modelsmechanism with
rule-based access policies (ADR-0022). Newaccess_policiestable
(subject_type tool/model/playbook, glob subject, allow/deny, JSONB
conditions on model/header/client_host, priority, enabled) evaluated by
the built-inAccessRuleEnginethrough the three AccessPolicy ports —
default-wired whenever persistence is configured, TTL-cached, no
restriction and no meaningful overhead when no rules exist; unmatched
subjects with rules are denied (allowlist semantics), errors fail closed.
Tool/Playbook policy ports now receive the effectivemodeland a
nullable caller (Experimental ports — permitted break).allowed_models
is fully removed:resourcescolumn dropped (migration 0002), gone from
ResourceDef, admin UI, playbook YAML (plus the never-resolved
access_policyhint field). Admin UI gains an Access Policies CRUD
section (/admin/policies). Docs: ADR-0022 + amended ADR-0013 +
workspace.dsl, rewritten access-policies page, updated tools/playbook/
concepts/roadmap pages and the nlght skill. Tests: rule-engine semantics,
rule repository, admin CRUD, catalog integration on real SQLite.
(slice: working/access-policy-rules) -
docs: point all demo references at the standalone demo repository
-
docs: point all demo references at the standalone demo repository
(https://github.com/Amphidrom/nlght-ai-demo) — the demo is no longer a
demo/directory in this repo. Quickstart now clones the demo repo;
recipes/extension-points links updated. Dropped the deaddemoentry
from pytestpythonpath. (slice: docs/demo-repo-links) -
build: move dev dependencies from a
devextra to a PEP 735
[dependency-groups]group. Theallextra stays user-facing but now
aggregates only runtime extras (docker … stores) — dev tooling is no
longer installable via an extra. CI installs adapted:tests.ymluses
uv sync --locked(dev group is uv's default group),release.ymluses
pip install -e .+pip install --group dev(pip ≥ 25.1, guaranteed by
the preceding pip upgrade). README dev-setup updated to match. The mypy
overrides for optional-extra packages additionally set
follow_imports = "skip"so typechecking results no longer depend on
which extras happen to be installed locally (spacy → numpy PEP 695 stubs
brokemypy srcunderpython_version = 3.11); three now-redundant
type: ignorecomments in the docker OS-runtime adapter were removed.
(slice: working/release-gates) -
ci(release): the release pipeline now also runs
tests/integrationand
tests/architecturebefore building distributions — previously only
tests/unitgated a release. (slice: working/release-gates) -
chore(dev): bump the local
misePython pin 3.12 → 3.13; CI still tests
the 3.11 floor (release) and 3.12 (tests workflow),requires-python
unchanged at >=3.11. (slice: working/release-gates) -
docs: introduce a "Capability Management" umbrella — Tools, Playbooks, and
Access Policies are documented as three independent sibling concerns, none
subordinate to another.concepts.mdxgains a "Capability Management"
overview and a dedicated "Access Policy" section (previously undocumented
there). Movedaccess-policies.mdxout from under
documentation/playbooks/to a top-leveldocumentation/access-policies
page and regroupedsidebar.jsonaccordingly.roadmap.mdx's "Access
Policies" entry andworkspace.dsl'saccessPolicyExtensionsnode were
already modeled correctly (independent of Playbooks) and needed no change.
(slice: working/experimental-stabilization) -
docs: split the Built-in Tools stability label —
shell,git, and
directive_managementare now documented as stable; onlyweb_search
keeps the Experimental badge, scoped to its page-content ingestion
mechanics (full-page enrichment, bot-masking,robots.txthandling),
which is unrelated to the separate Knowledge & Ingestion Pipeline
milestone.ToolContract/ToolCatalogport interfaces are now
documented as frozen (parameters already lower losslessly to JSON
Schema). Updatedroadmap.mdxand
documentation/tools/built-in-tools.mdx.
(slice: working/experimental-stabilization) -
test: close coverage gaps on the Experimental features flagged for
test-driven stabilization — Access Policy denial paths (allow/deny/
fail-closed-on-error/no-caller-skips-check) for tools and models,
max_hopsfallback-step edge cases (configured fallback, disabled
fallback hard-stops, exact-boundary no-overrun), Prometheus metering
(all standard metric families asserted, missing-dependency clear error,
duplicate metric-name/label-shape conflict documented), and Directive
prompt rendering (all threeKnownDirectivemembers — language,
timezone, tone — verified through their real templates end-to-end via
SystemPromptBuilder, not just raw-value substring checks).
(slice: working/experimental-stabilization) -
docs(i18n): translate remaining German docstrings/comments in
src/
to English — full sweep, 26 files across 3 module-grouped commits
(ports/core; tools/playbooks/os_runtime/workspace adapters;
sessions/trigger/config/hive_mind/wiring/executor). Comments and
docstrings only — no code logic, identifiers, or runtime string
literals touched. Deliberately left in German: keyword-matching data
inlayered_observe_extractor.py/artifact_extractor.py(matches
German user input, must stay German) and a German tool-description
string instores/vector.py(LLM-facing, not prose documentation).
Also translated one GermanImportErrormessage
(layered_observe_extractor.py) that the sweep initially left as
"runtime literal" but is actually developer-facing diagnostic text,
not functional data — fixed for consistency.
744 passed throughout, ruff + mypy clean. -
refactor(typing): finish the mypy
--strictcleanup — 186 remaining
mechanical errors (baredict/listtype-args, missing return-type
annotations,no-any-return/no-untyped-callcascades) down to
zero, repo-wide. Notable non-mechanical fixes found along the way:
admin/router.pyhad 22 errors from a single root cause (_tmpl()'s
missing return type made every route handler'sTemplateResponse(...)
type asAny);ModelProviderBackend.bind()/token_budget()
(ports/outbound/model_provider_backend.py) were fully untyped despite
the matchingSignalEmitter/ModelClient/TokenBudgetports already
existing — missed by the earlier Any-vs-Protocol audit since this port
wasn't in that grep's scope;web_search.py/keygen_license_adapter.py
gotisinstance/casttreatment forresp.json()'sAnyreturn,
scaled to how trusted each source is (user-configurable endpoint vs.
known API contract).
Addedmypy srcas a CI gate (.github/workflows/tests.yml) — it
wasn't checked in CI at all before; now that it's genuinely clean,
gating on it stops this from silently rotting again.
744 passed throughout, ruff clean.
(slice: working/mypy-strict-cleanup) -
fix: second round of real bugs found via mypy
--strict(207 -> 186,
remainder fully mechanical). Investigated every remaining non-mechanical
category (attr-defined,union-attr,override,arg-type) instead
of moving straight to the annotation sweep:layered_observe_extractor.py's__main__smoke-test called an
async defextract method withoutawait/asyncio.run— same
zero-test-coverage file as theExtractedArtifactbug from the
first round. Fixed.PlaybookCatalogport never declaredto_contract(), though
system_prompt.pycalls it and the one existing implementation
happens to have it — a conforming custom implementation (the whole
point of that extension point) would be missing it. Added to the
Protocol.HiveMindStoreCoordinatorFactory.__init__'sbackend: object | None
should have beenSessionBackend | None— slipped past the earlier
Any/object-vs-Protocol audit. Fixed.SimpleStoreCoordinator._dump()'s debug log called
atom.atom_type.value, butatom_typeis plainstrby design
(supports custom"namespace:label"types) — would crash on any
genuinely custom atom type. Fixed.WorkflowExecutor.stream/stream_signals(ports/outbound/workflow_executor.py)
were declaredasync def -> AsyncIterator[X], but the only
implementation is an async generator (noawaitneeded to call it)
— the Protocol's signature actively contradicted both real call
sites and would crash if followed literally. Fixed to match
ModelClient.stream()'s existing correct precedent.- Two
asserts added (passthrough.py,artifact_extractor.py)
where a public method'sNone-guard wasn't visible to mypy across
a private-helper boundary — real safety nets, not suppressions. - Full suite green throughout (744 passed), ruff clean.
(slice: working/mypy-strict-cleanup)
-
fix: multiple real bugs found while triaging mypy
--stricterrors
repo-wide (268 -> 207, remainder is mechanical bare-dict/missing
type-args, tracked separately):ExtractedArtifact(core/hive_mind/extraction.py) had nolanguage
field, butlayered_observe_extractor.pyconstructed it with
language=...and read.languageat 3 more sites — would crash
withTypeError/AttributeErrorevery time that code path ran.
Zero test coverage existed for that file. Added the field.persistence/models.py: a partial-unique-indexpostgresql_where
clause used the SQLAlchemy column typeText(...)instead of the
SQL-literal functiontext(...). The applied Alembic migration
already used the correctsa.text(...), so the DB schema itself
was fine, but the ORM model would mismatch on
alembic revision --autogeneratediffing. Fixed.docker.py:except type(None)is invalid Python and would raise
TypeErrorinstead of the intended fallback if ever hit. Added
self._container is not Noneassertions after 4 lazy-start call
sites (real safety net, not just type-checking noise).system_prompt.py:ctx: WorkflowStepContext = None(should be
| None), plus a byte-identical duplicate_build_active_phase
method silently shadowing itself.wiring.py:Container.licensehad adefault_factorythat isn't
a valid zero-arg callable (KeygenLicenseAdapterrequires
server_url/license_key) — latent (always passed explicitly in
practice, confirmed acrosssrc/and both non-unit test helpers),
but a real footgun. Changed toLicensingPort | None = field(default=None).- Root-caused the missing-type-stub errors instead of suppressing
per-line: added[[tool.mypy.overrides]]inpyproject.tomlfor
optional-extra third-party packages, andtypes-PyYAMLas a real
dev dependency (pyyamlis a required base dep). Found along the
way: the repo's own top-leveldocker/directory (Dockerfile
assets) shadows thedockerSDK package name via Python's
namespace-package resolution when the real package isn't installed
— not a runtime bug (a real installed package always wins), only
confuses mypy in the no-[docker]-extra dev environment. - Smaller fixes:
StoreCoordinatorFactory.save()override narrowing
documented (simple.py/coordinator.py), emitter-variable reuse
across mutually-exclusive branches renamed
(ollama_adapter.py/openai_adapter.py),_stream_as_ndjson
retyped to acceptAsyncIterable[Signal](whatQueuedSignalEmitter
actually implements via__aiter__, notAsyncIterator[Signal]),
AtomType._missing_now consistently coerces_value_tostr. - Full suite green throughout (744 passed), ruff clean.
(slice: working/mypy-strict-cleanup)
-
fix(model):
append_tool_turn()was silently broken for OpenAI Cloud,
Google, and Anthropic — none of the threeBoundXModelClientclasses
overrode it, so calls fell through to theModelClientProtocol's
empty stub and returnedNoneinstead of the updated message list.
Only Ollama (local + cloud) actually implemented it. Found while
chasing mypy--strict"Cannot instantiate abstract class" errors; no
test existed for this documented core feature on any provider before
now. Fixed all three: OpenAI delegates to the already-used-internally
append_openai_tool_turn; Anthropic delegates to
append_ollama_native_tool_turn(its existing
_convert_messages_for_anthropicalready expects that canonical shape
on input — confirmed by tracing whereappend_tool_turn's output
actually goes); Google's_to_google_contentsgained tool-turn
handling (previously had none at all) and
BoundGoogleModelClient.append_tool_turndelegates to the same
canonical helper. 7 new tests, including 2 round-trip tests proving
each fix's output is valid input for that same client's own converter
on the next call.
(slice: working/append-tool-turn-bugfix) -
refactor(typing): replace
Any/objectwith the matching port/core
type where one already existed, scoped from a 108-usage sweep of
src/.GitTool/ShellTool/HttpProbeTool._runtime()and their
os_runtimeparams →OsRuntime(all three had the identical untyped
accessor despiteToolBase.os_runtimealready being typed);
LoadMemoryArtifactTool.store_coordinator→StoreCoordinator | None;
ToolCatalogBuilder.build()'sstore_coordinator/workspace→
their real types;_tool_helpers.py'stool_catalog/contract/
parameters→ToolCatalog/ToolContract/list[ToolParameter];
artifact_ref_evaluator.stream_response()'semitter→SignalEmitter;
FrozenStoreCoordinator.__init__'sinner→StoreCoordinator.
Left legitimately-generic cases alone (free-formconfig: dict[str, Any],
**kwargs: Any,set_slot(value: Any)— value is meant to be arbitrary
JSON, the enum_missing_stdlib contract,MentalModel.delta's
intentionally-open duck-typed proxy). Net effect on the touched files:
mypy --strict errors 29 → 18 (fixing the params let mypy narrow 11
previously-hiddenReturning Anyerrors through the call chain).
This closes out the ports-and-adapters cleanup slice.
(slice: working/ports-and-adapters-cleanup) -
refactor(ports): add
ToolCatalogBuilder/PlaybookCatalogBuilder
Protocols (ports/outbound/tool_catalog.py/playbook_catalog.py) and
retypeStepMachineWorkflowExecutor's constructor params against them
instead of the concrete adapter classes. Not a pytestarch-caught
violation (both sides live inadapters/), but the same smell — and it
meant injecting a custom catalog builder required subclassing the
concrete YAML-driven implementation instead of just conforming to a
shape.BufferingSignalEmitter/QueuedSignalEmitterstay direct
concrete imports in the executor — selecting buffering vs. streaming is
its actual job, not a boundary issue.
(slice: working/ports-and-adapters-cleanup) -
fix(core): close the
core/workflow/llm.py→adapters.outbound.signals .buffering.BufferingSignalEmitterboundary violation — the most severe
of the 3 found (a runtimecore→adaptersimport, done as a
function-local import though no circular-import constraint required
it).call_llm()now uses a minimal private_BufferingCaptureclass
local tocore, structurally identical (emit()/collected()) but
dependency-free. Publiccall_llm()signature unchanged. Also typed
backend: objectasbackend: ModelProviderBackend | None— the
matching port already existed. All 10 architecture tests pass with no
xfails now (739 passed, matches the pre-slice baseline).
(slice: working/ports-and-adapters-cleanup) -
fix(ports): close the
ports/outbound/session_backend.py→
adapters.outbound.hive_mind.persistenceboundary violation (caught by
the corrected architecture test). MovedSessionSnapshotand its
_ser_*/_de_*(de)serialisation helpers +_slots_to_jsonfrom the
adapter intocore/hive_mind/models.py— it's a plain data shape built
from other core types (Directive/TurnSummary/SessionResult) with
json/datetime-only serialisation, no I/O.FileSystemBackendnow
imports it from core like every other consumer
(coordinator.py,pipeline_snapshot.py). Removed the corresponding
xfail— 737 passed, 2 xfailed (was 3), ruff clean. No ADR: this
enforces an already-established layering rule, not a new one (see
working-entry decision note); same precedent as the httpx→httpx2
migration.
(slice: working/ports-and-adapters-cleanup) -
refactor(config): consolidate
ProtocolAdapterConfig,GatewayConfig,
ModelProviderConfig,HiveMindProviderConfig(core/config/snapshot.py)
and their*Modeltwins (adapters/outbound/config/file/models.py) onto
sharedNamedResourceConfig/NamedResourceModelbase dataclasses — they
were byte-identical (name/kind/enabled/config). Each stays a
distinct, empty subclass for self-documenting signatures; consumers
already duck-type on these four attributes, so nothing downstream
changes.OsRuntimeConfig/OsRuntimeModel(nonamefield) and
WorkflowsPersistenceConfig/WorkflowsPersistenceModel(unrelated
shape) are not part of this — they don't conform.
(slice: working/config-dataclass-consolidation) -
refactor(config): remove dead
runtime_providers/RuntimeProviderConfig.
It was parsed fromplatform.yaml, mapped intoPlatformConfigSnapshot,
and never read anywhere — no registry consumed it, and the matching
RuntimeProviderRegistryport was never implemented or constructed.
RemovedRuntimeProviderConfig(core/config/snapshot.py),
RuntimeProviderModeland its YAML parsing
(adapters/outbound/config/file/{models,source,mapper}.py), and the
emptyports/outbound/runtime_provider_registry.py. First step of
consolidating the config layer's near-identical
name/kind/enabled/configdataclasses.
(slice: working/config-dataclass-consolidation) -
build: add a
uv.lockfor reproducible installs — no lockfile existed
before.uvpinned inmise.tomlalongside the existing Python 3.12
pin.tests.ymlCI now installs viaastral-sh/setup-uv@v5+
uv sync --locked --extra dev(fails on lock/pyproject drift instead of
silently re-resolving) and runs lint/tests throughuv run. README gets
a "Development" section documenting themise install/uv sync/
uv runworkflow.release.ymldeliberately left on plain pip — it
patchespyproject.toml's version in place before installing, which
would fight--locked's drift check; separate ADR-0021-documented
pipeline, out of scope here.
(slice: working/uv-lockfile) -
test(architecture): fix
tests/architecture/test_import_rules.py— the
pytestarch fixture passed identicalroot_path/module_path(both
src/), which silently breaks absolute-import resolution and made all
10 layer rules pass regardless of the actual code (verified with a
positive-control rule that also went undetected). Fixed to
module_path=src/nlght. This surfaced 3 real, previously-invisible
violations, markedxfail(strict=True)with the follow-up slice noted
in each reason:core/workflow/llm.pyimporting
adapters.outbound.signals.bufferingat runtime, and
ports/outbound/session_backend.pyreferencing a concrete adapter type
underTYPE_CHECKING. Suite stays green; fixing the violations
themselves is the next slice.
(slice: working/architecture-test-fixture-fix) -
docs: reframe the Hive Mind Directive Store as a foundational,
general-purpose layer for storing LLM communication ground-rules,
instead of reading as a language-only setting. Added a "Directive Store"
section to Concepts (language, timezone, tone — the current fixed
KnownDirectivevocabulary) and reworded the Roadmap's "Hive Mind —
Directives" entry to match. No code change —toneandtimezonewere
already implemented, only the docs undersold them.
(slice: working/directive-store-framing) -
rename: nlght's "Skill" concept is renamed to Playbook throughout code
and docs —SkillCatalog→PlaybookCatalog,ActiveSkill→ActivePlaybook,
SkillDefinition→PlaybookDefinition,SkillAccessPolicy→PlaybookAccessPolicy,
ctx.skills→ctx.playbooks,activate_skill→activate_playbook,
catalogs.skills→catalogs.playbooks. Pure rename, no behavior change.
Reason: avoid the terminology collision with Anthropic Agent Skills and
Claude Code Skills (nlght's own concept forces the LLM through a
prescribed procedure/phase sequence to reach an outcome — unrelated to
either of those). The Claude Code Skill mechanism itself (.claude/skills/,
the downloadableSKILL.md) is untouched — different concept, kept as-is.
(slice: working/playbook-extraction) -
tests: make the Docker-runtime unit tests hermetic — the test helper now
also forces_DOCKER_AVAILABLE = Truealongside itssys.modulesdocker
stub. Previously the two_sync_*tests failed in CI, where the optional
dockerpackage is not installed and the import-time flag stayed False. -
ci/release: tag-triggered release pipeline (
.github/workflows/release.yml).
PushingvX.Y.Z[-rcN]derives the PEP-440 version from the tag
(scripts/release/set_version.py), gates on ruff + unit tests, builds
sdist+wheel, publishes to PyPI via Trusted Publishing, pushes a stripped
public snapshot (scripts/release/public-exclude.txt) as one commit + tag
to github.com/Amphidrom/nlght-ai, rotates the change notes
(scripts/release/rotate_change_notes.py→
docs/change-notes/<version>.md), and commits version bump + rotation back
to main. Addstests.ymlCI (ruff + unit tests on push/PR) and unit tests
for the release scripts. Requires one-time setup: PyPI trusted publisher,
pypienvironment,PUBLIC_REPO_TOKENsecret, public repo. See ADR-0021.
(slice: working/release-pipeline) -
docs(site): GitHub links now point at the public repo
github.com/Amphidrom/nlght-ai (landing pages, recipes demo link, README
badge → public tests.yml). README: stale "free/enterprise tier" session
memory wording removed, docs pointer now targetsdocs/documentation/
(internal docs are stripped from the public snapshot).
(slice: working/release-pipeline) -
docs(roadmap): add the "Kubernetes Integration & Decoupled Worker Layer"
milestone — K8s deployment support, decoupling workflow execution from the
gateway process into a worker layer built for parallel runs, and on-demand
resource provisioning under load.
(slice: working/docs-rebrand-and-no-gating) -
licensing: remove capability gating. The
hive_mind.filesystemand
providers.multilicense checks are gone frombootstrap/wiring.py—
coordinator kind and provider count now depend only onplatform.yaml.
Startup still requires a valid license key (keygen validation + machine
registration; compliance only, tier-independent). The gating infrastructure
(LicensingPort, entitlements) stays as a hook for future enterprise
add-ons. Public docs rewritten to match: capability tier table, "silently
downgraded" claims, and the stalelicense_fileconfig removed; licensing
documented as compliance/machine registration with every feature available
in every tier (licensing, pricing, quickstart, platform-yaml,
configuration-model, session-memory, source-structure, ports-and-adapters,
roadmap). See ADR-0020 (amends ADR-0016); Structurizr workspace gains the
external License Server system —workspace.jsonstill needs regeneration.
(slice: working/docs-rebrand-and-no-gating) -
docs: rebrand all prose product-name mentions on the published site from
"nlght" to "nlght-ai" (documentation/**, landing pages, free-license page,
sidebar label "Extending nlght-ai"). Technical identifiers are untouched:
the import packagenlght,NLGHT_*env vars, thenlght:logger key,
paths, DB names, and GitHub/domain URLs. Also fixes aNLGHT__CONFIG
double-underscore typo in Quickstart.
(slice: working/docs-rebrand-and-no-gating) -
cli/packaging: rename the pip distribution to
nlght-aiand replace the two
console scriptsnlght-serve/nlght-migratewith a singlenlght-ai
command (nlght-ai serve,nlght-ai migrate [alembic-args…]; entry point
nlght.main:cli). Old script names are removed without aliases (pre-1.0
clean break). The import package staysnlght. Error messages now suggest
pip install 'nlght-ai[…]'; README and all user-docs command/pip references
updated. See ADR-0019. (slice: working/docs-rebrand-and-no-gating) -
docs: add a "Claude Code Skill" download + integration section to Quickstart
(/skills/nlght.md, served by the docs site; publishing the file itself is
tracked separately). Removed the staleToolSampleStep/TOOL_CALL:
text-parsing sample from Quickstart and the matching recipe — all four
ModelClientbackends (Ollama, Anthropic, OpenAI, Google) now run tool
calls natively when bound withtool_catalog=. Rewrote the Tool Calling
recipe, added a "Wiring a ToolCatalog into an LLM call" section to Custom
Tools (native loop vs step-driven loop), reworked the Tool section in
Concepts to match, and added a disambiguation note for nlght's own Skill
concept vs. a Claude Code Skill. Updated thenlghtagent skill's stale
"Tool calling" Common Pattern to the same native-loop approach.
(slice: working/docs-tooling-skills-cleanup) -
deps: migrate our direct HTTP usage from the unmaintained
httpxto its
maintained successorhttpx2(2.5.0, by Pydantic). Swapped the direct
dependency inpyproject.tomland renamed allhttpxreferences tohttpx2
across adapters (ollama, openai, licensing), built-in tools (fetch_url,
web_search),bootstrap/wiring,core/model/budget, and the matching
tests.httpxremains in the environment transitively (anthropic, openai,
google-genai, qdrant-client, weasel). Resolves the StarletteTestClient
StarletteDeprecationWarningat the root. (slice: working/httpx2-migration)