app-foundation
Host-neutral foundations shared by applications in an ecosystem.
Current Status
2026.11.06.1119 is released together with service-foundation, connection-hub and
project-board at one version (W322). It contains two
host-neutral foundations: MCP client construction and strict native
credential-value storage. The next release adds the outbound network policy
(app_foundation.net) and the A2A client (app_foundation.a2a), described
below.
python -m pip install 'app-foundation[mcp]'
python -m pip install 'app-foundation[native-secrets]'
python -m pip install 'app-foundation[data-bus]'
python -m pip install 'app-foundation[a2a]'
The package API is under app_foundation.mcp:
open_mcp_client(...)opens stdio, SSE, or Streamable HTTP transports;mcp_tool_schema(...)andnormalize_mcp_tool_result(...)provide stable Python values at the protocol boundary;connect_remote_tools(...)andprobe_remote_tools(...)provide a Streamable HTTP convenience surface for callers that already possess an endpoint and bearer.
Remote connection failures use stable, secret-safe codes. An HTTP 401 or
403, including one wrapped by the MCP transport's exception group, becomes
mcp_authorization_rejected; response bodies do not cross the boundary.
Timeouts and other connection failures retain their separate codes so a host
can retry connectivity without treating an outage as a credential verdict.
The caller remains responsible for credential custody, authority decisions, and product-specific error language. Supplying a bearer to the transport does not grant or evaluate authority.
The native value-store API is under app_foundation.secrets:
NativeSecretValueStore(...)selects one reviewed operating-system backend;replace(...),get(...), andremove(...)manage bounded text values;verify_ready()proves a disposable write, read, and removal;NativeSecretErrorexposes fixed, secret-safe error codes and messages.
The accepted backends are macOS Keychain
(keyring.backends.macOS.Keyring), Windows Credential Manager
(keyring.backends.Windows.WinVaultKeyring), and Linux Secret Service
(keyring.backends.SecretService.Keyring). Null, fail, chainer, file, and
wrong-platform backends are rejected. Windows values use a versioned,
integrity-checked chunk manifest so replacement either selects one complete
new generation or leaves the previous generation readable. The shared bound
is 288 KiB of UTF-8 text, which covers the largest OAuth record accepted by
the Connection Hub CLI after JSON escaping.
The shared store knows only service names, account keys, and text values. A consuming product owns serialization, logical namespaces, access policy, recovery, and the meaning of each secret.
The Data Bus client API is under app_foundation.data_bus:
DataBusClaim.from_mapping(...)validates a short-lived, bundle-scoped claim without rendering its token;FederatedDataBusClient.connect()opens the authenticated Socket.IO lane;close()stops either a connected socket or an in-progress reconnect loop and waits until the transport task has ended;request(...)distinguishes ingress acceptance from the handler's correlated terminal result;request(..., timing_observer=...)optionally calls a synchronous local observer with immutableRequestTimingvalues: existing UUID, fixed phase, UTC boundary and elapsed local monotonic seconds only. The default is no observer. It never passes payloads, subjects, object refs, credentials, idempotency keys, results or exception text. Observer exceptions (including observer cancellation) cannot replace the request result or cancellation; accidentally async observers are not scheduled. Non-UUID legacy IDs remain supported by transport but are not observed. Consumers must supply their own nonblocking, bounded admission gate.client.wait.resumedmeans the awaiting caller resumed, not the exact wire-arrival timestamp;wait_for_event(...)receives application push events that are not replies to an in-flight request.
The client constructs transport envelopes and correlates replies. The
application owns subjects, operation names, domain authorization, and the
meaning of pushed events. It refuses an expired claim before connection and
bounds event waits by claim expiry. Namespace admission has a configurable
30-second default, on both the initial connection and App Foundation-owned
reconnect attempts, so server-side Card verification and delivery of its
completion packet are not cut off by the Socket.IO client's one-second default
or by a busy client event loop at the former 15-second boundary. On deadline
the client retires and closes that transport once. Each App Foundation-owned
attempt has a distinct transport identity, so an acceptance or refusal queued
by an earlier attempt cannot complete a later attempt. This connection-only
bound does not change ingress or correlated-outcome timeouts. A Data Bus receipt
broadcast by the session for another peer is ignored by this client; it cannot
wake an application event loop. An ingress acknowledgement timeout leaves
acceptance unknown; a terminal result received before that timeout is returned.
When the timed-out transport is still the active App Foundation-owned one,
delivered nothing since the request was sent, and the timer fired on time, the
client treats it as silent and drops it through the normal disconnect path, so
the reconnect starts at once instead of when Engine.IO gives up. The client
keeps the old transport until its resources are released: a bounded graceful
disconnect, then an abort that closes its WebSocket and HTTP session, also when
the disconnect hangs or the client is closed. Requests already waiting for
their outcome keep waiting and resolve on the reconnected session. transport_recovering tells an owner that a drop is only transport:
the client is reconnecting on its own, within a bounded window, with no
handshake refused in the episode and an unexpired credential. Each reconnect
handshake logs the time since the drop, the transport open time and the
credential source time.
An accepted request without a terminal result is also outcome unknown. In
either unknown case, the product preserves the original message and operation
identity for a retry. Client errors include the logical connection generation,
Socket.IO connection ID, and connection state captured when the request began.
The client logs successful connections, disconnects, reconnects, and refused
connection attempts with the same generation and connection ID. A refused
attempt explicitly records whether it replaced the last successful generation,
so a product can correlate one request without treating reconnect activity as
proof that its connection changed. A product that replaces client objects may
provide non-secret lifecycle_labels; those labels appear on every lifecycle
record and let the product carry its stable channel identity and replacement
epoch across object-local generation resets.
A failed App Foundation-owned reconnect attempt is retried by default, with a doubling delay up to a cap, until the client is closed. An owner that knows which refusals cannot heal by retrying passes two optional constructor arguments:
refusal_classifier(error) -> boolis called with each failed reconnect attempt's exception and returns True when the refusal is permanent (for example, a revoked credential). The client knows no product codes; the owner supplies them all.on_terminal_refusal(record)is called once, when the classifier first answers True, with the same recordterminal_refusalreturns.
Both callbacks are synchronous: they run on the client's event loop, must not
block, and must not await. A classifier that raises is logged and its answer
is taken as transient, so a fault in the owner's code never ends the
reconnect. An exception from on_terminal_refusal is logged and does not
change the terminal state.
After a permanent refusal the client is terminal. terminal_refusal returns
state refused_permanent with the error code and type,
transport_recovering is False, and the reconnect loop has stopped. A
terminal client stays disconnected until it is closed: connect() raises
DataBusClientError with the stored code and the terminal_refusal record
in its details, without an attempt, and no disconnect or reconnect path opens
it again. There is no reset. The owner closes the client and creates a new
one, for example with a new credential. Without a classifier, behaviour is
unchanged: every failure is retried until close.
Outbound network policy
app_foundation.net is the one policy for calling a URL a person typed in.
Connection Hub's remote MCP connectors and the A2A client both use it.
OutboundEndpointPolicy.validate(url)returns the canonical URL or raisesOutboundEndpointDeniedwith a fixedreason. The rules:- HTTPS only, unless the deployment sets
allow_http; - no userinfo and no fragment;
- every resolved address must be global, unless the host is in the
deployment's
allowed_hostsor private networks are allowed.
- HTTPS only, unless the deployment sets
guarded_async_transport(policy)returns anhttpx2transport that pins each connection:- every socket resolves through the same policy and connects to the address it checked, so a second DNS answer cannot move it;
- TLS still verifies the certificate against the original host name;
- proxy variables are not used, unix sockets are refused, and it never retries.
A2A client
app_foundation.a2a (extra [a2a]) is a small client for the Agent2Agent
protocol 1.0 over JSON-RPC.
python -m pip install 'app-foundation[a2a]'
A2AClient.fetch_card(url)fetches and validates a public Agent Card. It returns aPinnedAgentCard: the JSON-RPC endpoint for protocol 1.x, bounded skills with safe ids, security scheme names and kinds, and a digest over exactly those fields. No credential is sent with the card request.send_message(non-blocking,returnImmediately),get_taskandcancel_taskcall the pinned endpoint with the caller's headers. They return anA2ATask: state, a bounded status text, and artifacts with text and data parts only. Raw, url and file parts are dropped and counted.
The boundary:
- Every URL, the card's endpoint included, passes the outbound policy.
- Caller headers go only to the pinned endpoint.
- There are no redirects, no cookies between calls, and reads stop at a byte limit.
- A failure is an
A2AClientErrorwith one fixed code. The remote agent's error messages, error data, HTTP bodies and status texts never become part of an error. - Task content is returned as data. The caller treats it as untrusted input, and owns credentials and every authority decision.
Boundary
Applications serving real users repeatedly need the same host capabilities:
- principal and service-identity contracts;
- secret-reference resolution and vault adapters;
- Postgres and Redis clients, cache, compare-and-set, and distributed locks;
- HTTP, CSRF, and external-URL utilities;
- events and observability primitives;
- protocol clients and neutral result conversion.
app-foundation owns reusable application-facing mechanisms. Product
authority, application-domain behavior, standalone process lifecycle, and
deployment orchestration remain outside this package.
app-foundation does not import service-foundation. The two distributions
can be composed by a product without creating a dependency cycle.
Extraction Contract
The production implementations being separated live in KDCube. Each extraction moves one verified contract into this package, then leaves the former KDCube path as a thin compatibility import. KDCube's MCP agent adapter and distributed server defaults remain platform-owned; they are not part of this client extraction.
License: MIT. Source: https://github.com/elenaviter/app-ecosystem
Metadata
Release files for app-foundation 2026.11.6.1119
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 |
|---|---|---|---|---|
| app_foundation-2026.11.6.1119.tar.gz | 109.5 kB | With provenance | Details for app_foundation-2026.11.6.1119.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| app_foundation-2026.11.6.1119-py3-none-any.whl | Python 3 | none | any | Details for app_foundation-2026.11.6.1119-py3-none-any.whl |
Total release size: 186.5 kB
Release files / app_foundation-2026.11.6.1119.tar.gz
| Download URL | app_foundation-2026.11.6.1119.tar.gz |
|---|---|
| Size | 109.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6dfcd96d23517866064f67540bce7aa632bf2e631e71ede73c78046d0e73624f
|
|
BLAKE2b-256 checksum How to use checksums |
d0d847f795fd0b3d69c22d61e086d3a9f4264a55435a36fd35d42a11811df358
|
| 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 6, 2026.
Transparency logRelease files / app_foundation-2026.11.6.1119-py3-none-any.whl
| Download URL | app_foundation-2026.11.6.1119-py3-none-any.whl |
|---|---|
| Size | 77.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
239c1a67067ee97f6ac5e8d7c1885fabf51275aab4711faa0950839f63dbdafd
|
|
BLAKE2b-256 checksum How to use checksums |
cf4a357394a8fef2c527e64991c332ba66e2642fb97c060f5b6067834ec0380e
|
| 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 6, 2026.
Transparency logSecurity for app-foundation 2026.11.6.1119
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 app_foundation-2026.11.6.1119.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 6, 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.