Metadata-Version: 2.4
Name: szns-springboard-runtime
Version: 0.0.1
Summary: Cross-cutting runtime for Springboard-generated ADK agents: identity, sessions, memory, plugin, tools.
Author: SZNS
License: Proprietary
Project-URL: Homepage, https://github.com/SZNS/szns-springboard-runtime
Project-URL: Issues, https://github.com/SZNS/szns-springboard-runtime/issues
Keywords: szns,springboard,adk,agents
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: adk
Requires-Dist: google-adk>=1.18.0; extra == "adk"
Provides-Extra: firestore
Requires-Dist: google-cloud-firestore>=2.16.0; extra == "firestore"
Requires-Dist: cachetools>=5; extra == "firestore"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-cov>=4; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: mypy>=1.8; extra == "dev"
Requires-Dist: google-adk>=1.18.0; extra == "dev"
Requires-Dist: google-cloud-firestore>=2.16.0; extra == "dev"
Requires-Dist: cachetools>=5; extra == "dev"
Dynamic: license-file

# szns-springboard-runtime

Python package that Springboard-generated ADK agents install at deployment time. Provides the canonical inbound message contract, identity types, the inbound adapters (Slack + HTTP), the principal resolver, the `SpringboardPlugin` that wires identity into the ADK runner, and helpers that tools use to read identity from session state.

See [ARCHITECTURE.md](./ARCHITECTURE.md) for the data model and end-to-end flow.

## Layout

```
szns_springboard_runtime/
├── __init__.py                      set/get/clear_canonical_context, requires_canonical_context
├── plugin.py                        SpringboardPlugin (ADK BasePlugin) — the seam
│                                    that writes session.state["szns"]
├── _internal/canonical_context.py   set/get/clear contextvar, requires_canonical_context
├── identity/
│   ├── context.py                   CanonicalInboundMessage, IdentityContext,
│                                    ActiveActor, Conversation, Source,
│                                    MessageContent, Correlation, Attachment,
│                                    AuthConfidence, Platform, Visibility,
│                                    VisibilityViolationError
│   ├── state.py                     build_szns_state, actor_to_dict, actor_from_dict
│   └── resolver.py                  PrincipalResolver, ResolvedPrincipal,
│                                    InMemoryPrincipalResolver (deterministic),
│                                    FirestorePrincipalResolver,
│                                    PrincipalNotFoundError
└── adapters/inbound/
    ├── slack.py                     from_event(payload)
    └── http.py                      from_request(body)

demo_generated_agent.py              runnable end-to-end demonstration
tests/                               pytest suite (adapters, resolver, state, plugin)
```

## How a generated agent wires it in

The plugin mirrors the Almanac plugin pattern — attach it to the runner, and
have each entryway hand it a `CanonicalInboundMessage`:

```python
from szns_springboard_runtime import set_canonical_context
from szns_springboard_runtime.adapters.inbound import http, slack
from szns_springboard_runtime.plugin import SpringboardPlugin

runner = Runner(..., plugins=[springboard_plugin])  # one instance, module-level

# In each inbound handler, before runner.run_async(...):
set_canonical_context(slack.from_event(payload))     # or http.from_request(body)
```

`SpringboardPlugin.before_run` resolves a stable, tenant-scoped `principal_id`
and writes `session.state["szns"]`. For surfaces with no handler we control
(the ADK web UI, A2A) it synthesizes a degraded identity from the ADK
invocation, so identity is always present. Tools then read it:

```python
from szns_springboard_runtime.identity import IdentityContext

def my_tool(tool_context) -> str:
    identity = IdentityContext.from_tool_context(tool_context)
    return identity.actor.principal_id   # canonical; same across entryways
```

The plugin imports `google-adk` (always present in a generated agent); install
it for local plugin/demo work via the `adk` or `dev` extra.

## Installation

```bash
pip install -e ".[dev]"
```

## Usage

```bash
python demo_generated_agent.py
```

## Development

```bash
pytest -v
ruff check .
ruff format --check .
mypy szns_springboard_runtime
```

## Implemented

- Canonical inbound contract + identity types (`CanonicalInboundMessage`, `IdentityContext`, …)
- Inbound adapters: Slack (Events API + slash commands) and HTTP (generic HTTPS trigger)
- `InMemoryPrincipalResolver` — deterministic, tenant-scoped canonical ids (stable across stateless instances) with cross-channel `register()` linking
- `SpringboardPlugin` — resolves identity and writes `session.state["szns"]` on every run; synthesizes a degraded identity for surfaces with no inbound handler (ADK web, A2A)

## Not yet implemented

- `SznsSessionService` (extends ADK `SessionService`) / `SznsMemoryService`
- Governance / OTel / model-armor plugins and tool wrappers (`with_confirmation`, `with_retry`, `with_scope`)
- Real visibility classifier (MVP stamps `private`) and `auth_confidence` enforcement
- Channel adapters for email (Slack + HTTP done; ADK web / A2A via plugin fallback)
- Outbound adapters
- `FirestorePrincipalResolver` implementation (interface scaffolded; stub raises `NotImplementedError`) — required for cross-*provider* identity continuity across stateless instances
- Pydantic migration of identity types

## Open configuration questions

- `auth_confidence` determination policy. The Slack adapter currently assigns `WORKSPACE_VERIFIED` to any signed Slack request; HTTP assigns `OBSERVED_EMAIL` / `ANONYMOUS`.
- `conversation_id` fallback when `thread_ts` is absent. Current chain: `thread_ts` then `ts` then channel-only.

## References

- [ARCHITECTURE.md](./ARCHITECTURE.md)
- Improved Architecture Tech Spec
- Runtime SDK Tech Spec
