adk-null-session
An invocation-local session service for Google ADK.
It returns independent Session objects without retaining them in the service.
Use it when every request supplies all the information needed to finish in a
single Runner invocation, including any ordinary function-call round trips.
Version 0.1.0 is an initial experimental package. It is not a transparent replacement for a history-preserving session service.
Install
From this checkout (Python 3.11 or newer):
python -m pip install .
The runtime dependency is google-adk>=1.27.0,<3. This range targets both ADK
1.x and 2.x; it does not claim that every release or dependency combination has
been tested. The package does not directly require A2A SDK. Install the A2A
extras appropriate to your ADK version separately if your server uses A2A.
Use with Runner
This example runs locally without model credentials or network calls:
import asyncio
from google.adk.agents.base_agent import BaseAgent
from google.adk.events.event import Event
from google.adk.runners import Runner
from google.genai import types
from adk_null_session import NullSessionService
class EchoAgent(BaseAgent):
async def _run_async_impl(self, ctx):
yield Event(
author=self.name,
invocation_id=ctx.invocation_id,
content=types.Content(
role="model",
parts=[types.Part(text=ctx.session.state["request_text"])],
),
)
async def main():
runner = Runner(
app_name="example",
agent=EchoAgent(name="echo"),
session_service=NullSessionService(),
)
async for event in runner.run_async(
user_id="user",
session_id="request-1",
new_message=types.Content(
role="user", parts=[types.Part(text="Hello")]
),
state_delta={"request_text": "Hello"},
):
if event.content:
for part in event.content.parts or []:
if part.text:
print(part.text)
asyncio.run(main())
create_session() is optional for this service: get_session() accepts any ID.
If you call create_session(state=...), that state exists only in its returned
object. Runner retrieves a new empty object through get_session(), so the
initial state is lost before the first invocation. Pass per-request values
through run_async(state_delta=...), as above, or include them in the request.
Explicit URI registration
from google.adk.cli.service_registry import get_service_registry
from adk_null_session import NullSessionService, register_null_session_service
register_null_session_service()
service = get_service_registry().create_session_service("null://")
assert isinstance(service, NullSessionService)
Call registration in the same Python process before building your ADK server
and selecting null:// as its session service URI. You can also pass an explicit
ServiceRegistry instance to register_null_session_service(registry).
The factory ignores URI contents and keyword arguments. Registration replaces
any existing null factory in that registry. Installing or importing the
package does not register a URI scheme, start uvicorn, or configure logging.
Running an unrelated adk web --session_service_uri null:// process after
installation alone will not perform this registration.
Behavior
| API | Result |
|---|---|
create_session(...) |
New unsaved session; trims a supplied ID or generates a UUID for a missing/blank ID. Copies supplied initial state. |
get_session(...) |
New, empty, independent session with exactly the requested ID on every call; event filters have no stored events to filter. |
list_sessions(...) |
Empty ListSessionsResponse. |
delete_session(...) |
No operation. A later lookup still returns an empty session. |
get_user_state(...) |
New empty dictionary, including on ADK versions predating the base API. |
append_event(session, event) |
Inherited ADK behavior: updates the supplied session's events and state during the invocation; partial events are not committed. |
The service holds no session references. Ordinary function calling, tool state,
and agent output_key values remain available within a single invocation.
app: and user: state are also invocation-local; neither is shared across
requests. Duplicate IDs are accepted. Unknown and deleted IDs look like empty
sessions, so this service cannot support existence checks, session browsing,
or tools such as Web UI history/evaluation that require stored sessions.
The inherited append_event does not advance last_update_time in the tested
ADK releases. The timestamp records construction time, not the latest event.
Events and state still occupy memory and can grow during an invocation.
Limitations that affect correctness
- No multi-turn memory. Each request must carry the full information it
needs.
RemoteA2aAgentcan reuse a returnedcontext_idwithout sending earlier history. Evenfull_history_when_stateless=Truedoes not force full history when a context ID exists in the tested releases. Replacing an A2A server's session store can therefore silently remove context needed by later turns. - No continuation. Human-in-the-loop approval, additional authentication,
follow-up results for
LongRunningFunctionTool, resume, and rewind require history this service discards. In tested ADK 1.x, resuming byinvocation_idalone raises a missing-history error. In ADK 2.13.0, anLlmAgentcan instead invoke its model again; a scripted repeated tool request reproduced duplicate execution (the in-memory control executed once). Do not use this service for these flows. The service cannot reliably detect resume fromget_sessionarguments, which contain no invocation ID, and does not provide a resume guard. - No server-wide retention or memory guarantee. A2A's
InMemoryTaskStoreis separate. In a ten-request probe, all ten completed Tasks still held history and artifacts. Other stores, logs, traces, caller references, and active invocations can retain data independently. Configure their policies separately.
Compatibility and verification
The initial minimum is ADK 1.27.0, rather than all 1.x versions. In 1.25.0,
base append_event fails to pass temp:output_key to a later agent; the same
failure occurs with InMemorySessionService. It was fixed by upstream commit
2780ae2
and the probe passes on 1.27.0, 1.39.1, and 2.13.0. Python >=3.11 is chosen to
cover the minimum required by the tested ADK 2.x release.
The original implementation
was reviewed against source tags 1.25.0, 1.27.0, 1.39.1, and 2.13.0: 89 passed,
3 skipped over 23 cases per tag. The probes include assertions of unsupported
behavior, so passing does not certify suitability for those flows. This package
adds an explicit empty get_user_state implementation and copies initial state.
Its tests preserve the behavior probes and add checks for the package API.
The installed 0.1.0 wheel passed 31 tests per tag (93 total) on 1.27.0,
1.39.1, and 2.13.0; the Runner example above also ran successfully on each tag.
The original and package source-tag checks use Python 3.13.18, a shared
environment with a2a-sdk 0.3.25, google-genai 2.20.0, and pydantic 2.13.4.
They exercise real Runner/tool/A2A handler code with scripted model responses,
20 concurrent invocations, and session-reference release. This is a source
behavior check, not a resolved dependency matrix: for example, that shared
google-genai version falls outside the declared ranges of ADK 1.27.0 and
2.13.0. No external LLM, HTTP transport, full dependency matrix, other Python
version, or production load test was exercised.
Development
The project follows
cookiecutter-develop-pypackage 0.4.0,
with project_dir_name=adk-null-session, package_name=adk_null_session, and
use_taskipy=no. It uses setuptools and reads the package version from
adk_null_session.__version__. The existing Apache-2.0 license and service
implementation are retained; no command-line application is provided.
Create an isolated environment and run the same checks used in CI:
python -m venv .venv
source .venv/bin/activate
python -m pip install -U pip
python -m pip install -e '.[dev]'
ruff check .
python -m pytest -v
python -m build
python -m twine check --strict dist/*
The dev extra includes pytest-randomly, async/A2A test dependencies, Ruff,
build, and twine. The smaller test and test-a2a extras remain available.
CI runs on pushes and pull requests using Python 3.11–3.14 with freshly resolved
dependencies. A green CI run checks that resolved environment; it does not
establish compatibility with every ADK release in the declared dependency range.
In addition to the earlier shared-environment probes, template integration was verified in a fresh Python 3.13.18 environment with installed google-adk 2.13.0, google-genai 2.34.0, pydantic 2.14.2, and a2a-sdk 0.3.26: all 31 tests passed. Other Python versions in the CI matrix have not been run locally.
A2A-specific tests skip when A2A SDK is unavailable. Tests make no external model
calls. Build artifacts include the implementation and tests, not exported ADK
source trees. The public API is NullSessionService and
register_null_session_service from adk_null_session.
Based on the author's article. Licensed under the existing Apache License 2.0.
Release preparation
Update __version__ in src/adk_null_session/__init__.py and the version stated
in this README, then run the development checks and review the built wheel and
source distribution. Commit the intended release changes and wait for CI to
pass before publishing a release.
Before the first upload, configure these external settings manually:
- Create the GitHub repository environment named
pypiand configure its protection rules as appropriate for releases. - On PyPI, configure a Trusted Publisher (or a pending publisher for the first
release) for project
adk-null-session, ownerftnext, repositoryadk-null-session, workflow filenamepublish.yml, and environmentpypi. See the PyPI instructions.
Publishing a GitHub Release tagged v0.1.0 triggers
.github/workflows/publish.yml. For later releases, the tag must be v followed
by the package version. Its build job creates and checks both distributions and
rejects a mismatched release tag. A separate job downloads those artifacts and
uploads them to PyPI using OIDC; only this job has id-token: write. No PyPI API
token is required. This workflow is for PyPI, not TestPyPI.
Adding these files does not configure a PyPI publisher or perform an upload. Tagging, publishing the GitHub Release, and completing external setup are separate maintainer actions. The workflow has been validated locally; the GitHub-hosted job and actual OIDC upload still need verification during a release.
After changing workflow actions, run pinact run -u, review the updated action
versions and full SHA pins, and run actionlint. The PyPI publishing action uses
a versioned release tag comment because pinact cannot resolve the template's
release/v1 branch.
Metadata
Release files for adk-null-session 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| adk_null_session-0.1.0.tar.gz | 21.0 kB | With provenance | Details for adk_null_session-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| adk_null_session-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for adk_null_session-0.1.0-py3-none-any.whl |
Total release size: 33.8 kB
Release files / adk_null_session-0.1.0.tar.gz
| Download URL | adk_null_session-0.1.0.tar.gz |
|---|---|
| Size | 21.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bfdf991f8062ba11d99655a8be87d8ec9d057b1748376b72a065ea32ac420091
|
|
BLAKE2b-256 checksum How to use checksums |
d41ea6569ba8fa0eee725d15ce4626f67198354f76157d21c1948c836de843a5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logRelease files / adk_null_session-0.1.0-py3-none-any.whl
| Download URL | adk_null_session-0.1.0-py3-none-any.whl |
|---|---|
| Size | 12.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e9e7fcf517bbc5f234fefe600c3b18202f3850f86cae6e535fb60d4d506242f1
|
|
BLAKE2b-256 checksum How to use checksums |
5e06e8816f7581e426e8321b7683fa458b2d26811d1a3d488891761f170c8c19
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logSecurity for adk-null-session 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for adk_null_session-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.