Skip to content

feat(instrument): Port Agentforce framework adapter (M2)#108

Closed
mmercuri wants to merge 2 commits into
feat/instrument-base-foundationfrom
feat/instrument-frameworks-agentforce
Closed

feat(instrument): Port Agentforce framework adapter (M2)#108
mmercuri wants to merge 2 commits into
feat/instrument-base-foundationfrom
feat/instrument-frameworks-agentforce

Conversation

@mmercuri

Copy link
Copy Markdown
Contributor

Summary

Ports the Salesforce Agentforce framework adapter from ateam (stratix.sdk.python.adapters.agentforce, ~2,954 LOC across 11 files — the largest of the M2 framework batch) onto the layerlens.instrument base layer landed in M1.A (#93). Self-contained: this PR can be reviewed and merged independent of the other M2 fan-out PRs.

Source path: A:/github/layerlens/ateam/stratix/sdk/python/adapters/agentforce/ (adapter, auth, client, events, importer, llm_eval, mapper, models, normalizer, trust_layer, __init__).

Template: langchain framework adapter — same package layout (per-concern modules, ADAPTER_CLASS lazy-loading marker, __all__ reexport, requires_pydantic compat hint).

Salesforce Agentforce specifics

This adapter is import-mode rather than runtime monkey-patching: Agentforce is a remote multi-tenant REST service, not a Python library, so there is no in-process SDK to instrument. The adapter:

  • Authenticates with OAuth 2.0 JWT Bearer (PyJWT + RS256). SalesforceCredentials accepts the private key as raw PEM, an env:NAME reference, or a filesystem path.
  • Backfills traces by running SOQL against five Data Cloud DMOs (AIAgentSession, AIAgentSessionParticipant, AIAgentInteraction, AIAgentInteractionStep, AIAgentInteractionMessage).
  • Exposes companion modules for Agent API REST real-time capture (client.py + mapper.py), Platform Events subscription via gRPC Pub/Sub with CometD long-polling fallback (events.py), Einstein Trust Layer policy round-trip (trust_layer.py), and an LLM evaluator (llm_eval.py).
  • Includes hardening from the original port: SOQL injection guards (every parent ID validated against the 15/18-char Salesforce ID regex; date / timestamp params validated against ISO 8601 regexes), token re-authentication on expiry, and Sforce-Limit-Info rate-limit warnings at 80% consumption.
  • to_stratix_policy(...) is retained as a DeprecationWarning alias for to_layerlens_policy(...) so existing stratix.* callers still work for one migration window.

What's in the PR

Path Purpose
src/layerlens/instrument/adapters/frameworks/agentforce/ (11 files) Full port of the source package
src/layerlens/instrument/adapters/frameworks/__init__.py Lazy package marker (no eager SDK imports)
tests/instrument/adapters/frameworks/test_agentforce.py (36 tests) SDK-shape mocks; no real Salesforce or network call
samples/instrument/agentforce/main.py + README.md Runnable end-to-end sample (4 mocked flows + opt JWT)
docs/adapters/frameworks-agentforce.md Integration guide incl. OAuth Connected App setup
pyproject.toml New agentforce extra (requests + PyJWT[crypto])

M2 fan-out

This PR is part of the M2 framework adapter fan-out — one self-contained PR per heavy framework (Agentforce is the largest by LOC). It depends on M1.A (#93 — instrument base foundation) but is independent of the other M2 PRs.

Acceptance criteria status

  • mypy --strict src/layerlens/instrument/adapters/frameworks/agentforceSuccess: no issues found in 11 source files
  • ruff check src/.../agentforce tests/.../test_agentforce.pyAll checks passed
  • pytest tests/instrument/adapters/frameworks/test_agentforce.py36 passed
  • pytest tests/instrument/test_default_install.py3 passed (the new agentforce extra does not perturb the default install set)
  • python samples/instrument/agentforce/main.pyexit 0, prints all four flow summaries

Test plan

  • CI: mypy --strict clean on the new package
  • CI: ruff check clean on the new package + test
  • CI: pytest tests/instrument/adapters/frameworks/test_agentforce.py green (36 tests)
  • CI: default-install guard (tests/instrument/test_default_install.py) green — confirms the agentforce extra is opt-in
  • Reviewer: read docs/adapters/frameworks-agentforce.md OAuth Connected App section against current Salesforce Setup screens
  • Reviewer: spot-check auth.py JWT signing payload + retry logic for parity with the source port
  • Reviewer: confirm to_stratix_policy deprecation alias matches the rename pattern adopted elsewhere in the M2 batch

Ports the Salesforce Agentforce framework adapter from ateam
(stratix.sdk.python.adapters.agentforce, ~2,954 LOC across 11 files —
the largest of the M2 framework batch) onto the layerlens.instrument
base layer landed in M1.A.

Scope
-----
- src/layerlens/instrument/adapters/frameworks/agentforce/ — full port
  of all 11 modules (adapter, auth, client, events, importer, llm_eval,
  mapper, models, normalizer, trust_layer, __init__)
- src/layerlens/instrument/adapters/frameworks/__init__.py — package
  marker that does NOT eagerly import any framework SDK
- tests/instrument/adapters/frameworks/test_agentforce.py — 36 unit
  tests (lifecycle, importer with paginated SOQL fixtures, normalizer
  for every DMO record type, Agent API client/mapper round-trip,
  Trust Layer YAML emission + deprecation alias, Platform Events
  handler, Einstein evaluator offline behavior, lazy-import guard).
  All mocks are SDK-shape only — no real Salesforce / network call.
- samples/instrument/agentforce/ — runnable end-to-end sample with
  4 mocked flows (SOQL backfill, live Agent API capture, Trust Layer
  policy export, evaluator offline) plus optional live JWT auth check.
- docs/adapters/frameworks-agentforce.md — integration guide including
  Connected App + JWT Bearer OAuth setup, event taxonomy, capture
  config, BYOK, Trust Layer round-trip, and replay semantics.
- pyproject.toml — new "agentforce" optional extra
  (requests + PyJWT[crypto]).

Salesforce specifics preserved from the source port
---------------------------------------------------
- OAuth 2.0 JWT Bearer flow with private-key resolution from env vars,
  filesystem paths, or inline PEM strings.
- SOQL injection guards: every parent ID interpolated into WHERE … IN
  clauses is validated against the 15/18-char Salesforce ID regex;
  date and timestamp params validated against ISO 8601 regexes.
- Token re-authentication on expiry, with X-RateLimit / Sforce-Limit-Info
  warnings at 80% consumption.
- Trust Layer policy export renamed to_layerlens_policy with a
  deprecation alias keeping to_stratix_policy callable for one
  migration window.

Verification
------------
- mypy --strict src/layerlens/instrument/adapters/frameworks/agentforce
  → Success: no issues found in 11 source files
- ruff check src/layerlens/instrument/adapters/frameworks/agentforce
  tests/instrument/adapters/frameworks/test_agentforce.py
  → All checks passed
- pytest tests/instrument/adapters/frameworks/test_agentforce.py
  → 36 passed
- pytest tests/instrument/test_default_install.py
  → 3 passed (extra does not change default install set)
- python samples/instrument/agentforce/main.py
  → exits 0, prints all 4 flow summaries

Refs LAY-INSTRUMENT (M2 fan-out)
@mmercuri mmercuri requested a review from m-peko April 26, 2026 08:44
Two corrections after running mypy --strict against a fresh resolved
environment:

- auth.py: drop the now-unused [arg-type] ignore on _check_rate_limit;
  response.headers from requests already typechecks against the
  dict[str, Any] parameter.
- events.py: include [import-not-found] alongside [import-untyped] in
  the optional grpc import; mypy resolves grpc to import-not-found when
  no stubs are installed (the default install path).

mypy --strict src/layerlens/instrument/adapters/frameworks/agentforce
  -> Success: no issues found in 11 source files
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants