Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
a984465
Add layerlens.attestation: cryptographic hash chains for tamper-evide…
garrettallen14 Mar 25, 2026
abf4151
feat: signing keys
garrettallen14 Mar 26, 2026
4c44731
refactor: remove client-side signing, delegate to server-side attesta…
garrettallen14 Mar 27, 2026
a6d9bbf
fix: attestation chain integrity: error propagation, async I/O, envel…
garrettallen14 Mar 27, 2026
4c5d860
feat: add BaseAdapter ABC, AdapterRegistry, and refactor all adapters…
garrettallen14 Mar 30, 2026
904067a
feat: replace span trees with flat event emission, add CaptureConfig …
garrettallen14 Mar 31, 2026
810671e
feat: cleanup + refactor instrumentation test package (#80)
garrettallen14 Apr 2, 2026
6c5817b
feat | unified context model, per-client uploads, and pre-ship harden…
garrettallen14 Apr 7, 2026
07925bc
feat | new adapters, 3rd iteration (#84)
garrettallen14 Apr 8, 2026
91a92b5
feat: agentforce, agno, autogen, bedrock adapters
garrettallen14 Apr 8, 2026
260c5c4
Merge remote-tracking branch 'origin/main' into development
garrettallen14 Apr 12, 2026
c56dcf6
fix: formatting, lint, and restore files deleted by merge
garrettallen14 Apr 12, 2026
758e82b
Merge pull request #87 from LayerLens/feat/new-adapters-4
garrettallen14 Apr 13, 2026
05331c3
Merge branch 'development' of https://github.com/LayerLens/atlas-pyth…
garrettallen14 Apr 13, 2026
eaae65b
fix: format new adapters from PR #87 (agentforce, agno, autogen, bedr…
garrettallen14 Apr 13, 2026
8c3ee42
fix: relax pyright for adapter frameworks/providers (optional deps)
garrettallen14 Apr 13, 2026
386e0c5
New adapters, protocols and vscode extension
m-peko Apr 20, 2026
a3406d0
Merge main into development
m-peko May 18, 2026
3d6ac8b
Add auto-detection to AdapterRegistry
m-peko May 18, 2026
847f022
Emit a SHA-256 state hash per LangGraph node
m-peko May 18, 2026
4eb5096
Detect agent-to-agent handoffs in LangGraph
m-peko May 18, 2026
aea9c98
W3C Trace Context propagation + OTel GenAI semconv
m-peko May 18, 2026
da9ede3
Detect CrewAI delegations as agent.handoff
m-peko May 18, 2026
a29badf
Add Microsoft Agent Framework adapter
m-peko May 18, 2026
312fb9c
Add embedding + vector_store adapters and a benchmark importer
m-peko May 18, 2026
9763cc8
Trace LangChain memory state changes
m-peko May 18, 2026
b53f606
Serialise replay-ready trace snapshots
m-peko May 18, 2026
046fc6d
Add ProtocolCertificationSuite
m-peko May 18, 2026
684e77d
ruff-format pass and restore lint suppressions
m-peko May 18, 2026
63bee2b
Fix CrewAI handler dispatch under crewai >=1.x
m-peko May 18, 2026
bac6c0b
OTel GenAI semconv: vendor-namespace attrs + Bedrock OTel wiring (LAY…
garrettallen14 May 20, 2026
7f7756c
Pricing: PricingTable class + fuzzy match + env-driven overrides (LAY…
garrettallen14 May 20, 2026
bdbc42d
Streaming: TTFT, partial-event on error, _streaming module, JSON warn…
garrettallen14 May 20, 2026
8eca122
Anthropic: privacy-safe params + thinking + block counts + message_st…
garrettallen14 May 20, 2026
c984ed8
Legacy adapter import-path compat shim (LAY-3326)
garrettallen14 May 20, 2026
c621c75
M2 adapter AC cleanup: extras, samples, docs, lazy exports (LAY-3446.…
garrettallen14 May 20, 2026
c31f3ca
M2: lazy-import regression test for frameworks/__init__.py (LAY-3446.…
garrettallen14 May 21, 2026
ea52bd4
M3 provider ports: Vertex + Ollama (LAY-3453, LAY-3454)
garrettallen14 May 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ Brewfile.lock.json

.DS_Store
.coverage
.venv*
docs/review/
101 changes: 101 additions & 0 deletions docs/adapters/frameworks/agentforce.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Agentforce adapter

Batch-imports [Salesforce Agentforce](https://www.salesforce.com/agentforce/)
sessions and interactions from Data Cloud Data Model Objects (DMOs). Unlike
the in-process framework adapters, Agentforce is observed post-hoc by
querying Salesforce, so the integration is OAuth-authenticated HTTP rather
than a callback or filter API.

## Install

```bash
pip install layerlens[agentforce]
```

Pulls `httpx>=0.27.0` for the Salesforce REST client.

## OAuth setup

The adapter authenticates with Salesforce via the **OAuth 2.0 Client
Credentials** flow. You'll need:

1. **A Connected App** in your Salesforce org with:
- OAuth scopes: `api`, `refresh_token`, `cdp_query_api`
- "Enable Client Credentials Flow" turned on
- A "Run As" user with permission to read `AIAgentSession__dlm`,
`AIAgentInteraction__dlm`, and `AIAgentConfiguration__dlm` on Data Cloud
2. **The Consumer Key** (client ID) and **Consumer Secret** (client secret)
from the Connected App
3. **Your org's My Domain URL** (e.g. `https://myorg.my.salesforce.com`)

In Setup → App Manager, create a new Connected App, enable OAuth settings,
add the scopes above, then under "Client Credentials Flow" assign a user to
run as. After saving, copy the consumer key/secret from "Manage Consumer
Details."

Pass the credentials to `connect()`:

```python
import os
from layerlens.instrument.adapters.frameworks import AgentforceAdapter

adapter = AgentforceAdapter(client=layerlens_client)
adapter.connect(
credentials={
"client_id": os.environ["SF_CLIENT_ID"],
"client_secret": os.environ["SF_CLIENT_SECRET"],
"instance_url": os.environ["SF_INSTANCE_URL"],
},
)
```

`connect()` performs the client-credentials token exchange against
`{instance_url}/services/oauth2/token` and caches the access token on the
adapter for subsequent queries.

## Usage

```python
adapter.connect(credentials={...})

# Incremental import. Pass the previous run's next_cursor for exactly-once.
summary = adapter.import_sessions(limit=50, since_cursor=previous_cursor)

print(summary["sessions_imported"], summary["events_emitted"])
next_cursor = summary["next_cursor"] # persist for the next run

adapter.disconnect()
```

`import_sessions` accepts `start_date`, `end_date`, `limit`, and
`since_cursor`. The returned `next_cursor` is the max `StartTime` seen, so a
caller can persist it and pass it back to incrementally sync without
re-importing.

## Event surface

Each Agentforce session becomes its own trace via `_begin_run` /
`_end_run`. Inside a session:

- `environment.config` — one event per session with the agent configuration
(model name, instructions, topic/action counts) pulled from
`AIAgentConfiguration__dlm`.
- `model.invoke` for LLM/generative steps (`StepType` ∈ {llm, model,
generative}), with prompt/completion token counts.
- `tool.call` for action/function/tool/flow steps, with tool name, input,
and output.
- `agent.handoff` for escalation/handoff/transfer steps, with the escalation
target.
- `agent.error` for steps with a non-empty `ErrorMessage`.

Step types are detected from the `StepType` field on
`AIAgentInteraction__dlm` and dispatched through `_STEP_DISPATCH`.

## Sample

[`samples/instrument/agentforce/example.py`](../../../samples/instrument/agentforce/example.py)

## Compat

- Salesforce REST API v62.0
- Python 3.9+
56 changes: 56 additions & 0 deletions docs/adapters/frameworks/autogen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# AutoGen adapter

Instruments [AutoGen](https://github.com/microsoft/autogen) agents and teams via
AutoGen's structured event logging API (autogen-core ≥ 0.4).

## Install

```bash
pip install layerlens[autogen]
```

Pulls `autogen-agentchat>=0.4.0` (and `autogen-core` as a transitive dep).

## Usage

```python
import asyncio
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.teams import RoundRobinGroupChat
from layerlens.instrument.adapters.frameworks import AutoGenAdapter

adapter = AutoGenAdapter(client=layerlens_client)
adapter.connect() # attaches a logging.Handler to autogen_core.events

async def run():
team = RoundRobinGroupChat([agent_a, agent_b])
await team.run(task="...")

asyncio.run(run())
adapter.disconnect() # removes the handler and flushes the trace
```

## Event surface

The adapter listens for AutoGen's structured event classes and emits:

- `model.invoke` for `LLMCallEvent` and `LLMStreamEndEvent` (provider-aware,
pulls the model name from the response payload).
- `tool.call` for `ToolCallEvent`, including tool name and arguments.
- `agent.message` for `MessageEvent` between participants.
- `agent.error` for `MessageDroppedEvent`, `MessageHandlerExceptionEvent`,
and `AgentConstructionExceptionEvent`.
- `conversation.ended` per topic/session when the trace tears down, with the
participant set, message count, and turn count.

Thread-safety: AutoGen dispatches log events from any thread, so the adapter
holds the collector and run state on the instance rather than via ContextVars.

## Sample

[`samples/instrument/autogen/example.py`](../../../samples/instrument/autogen/example.py)

## Compat

- autogen-agentchat 0.4+ (autogen-core 0.4+)
- Python 3.9+
51 changes: 51 additions & 0 deletions docs/adapters/frameworks/crewai.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# CrewAI adapter

Instruments [CrewAI](https://github.com/crewAIInc/crewAI) crews via CrewAI's
typed event bus (CrewAI ≥ 1.0). Earlier 0.x event-bus versions are also
handled via the dispatcher fallback.

## Install

```bash
pip install layerlens[crewai]
```

Pulls `crewai>=0.30.0`. CrewAI 0.30+ is Pydantic v2-only
(`requires_pydantic="2"` on the adapter).

## Usage

```python
from crewai import Agent, Crew, Task
from layerlens.instrument.adapters.frameworks import CrewAIAdapter

adapter = CrewAIAdapter(client=layerlens_client)
adapter.connect() # registers handlers on CrewAI's event bus

crew = Crew(agents=[...], tasks=[...])
crew.kickoff()

adapter.disconnect() # tears down handlers when done
```

## Event surface

- `agent.start` / `agent.end` per agent step.
- `task.start` / `task.end` per Crew task.
- `tool.call` for every tool invocation, with the tool name + arguments.
- `model.invoke` for the underlying LLM calls (provider-aware via the
CrewAI agent's `llm` attribute).
- `agent.handoff` when CrewAI delegates between agents.

Thread-safety: CrewAI dispatches handlers across threads, so the adapter
manages collector and span state on the instance rather than via
ContextVars.

## Sample

[`samples/instrument/crewai/example.py`](../../../samples/instrument/crewai/example.py)

## Compat

- CrewAI 0.30+ (Pydantic v2-only)
- Python 3.9+
49 changes: 49 additions & 0 deletions docs/adapters/frameworks/langgraph.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# LangGraph adapter

Instruments [LangGraph](https://langchain-ai.github.io/langgraph/) graphs with
LayerLens tracing. Subclasses the LangChain callback handler (M1.C reference
template) and adds graph-state hashing for replay.

## Install

```bash
pip install layerlens[langgraph]
```

Pulls `langgraph>=0.2.0` and `langchain-core>=0.1.0`. LangGraph 0.2+ requires
Pydantic v2 (`requires_pydantic="2"` on the handler).

## Usage

```python
from langgraph.graph import StateGraph
from layerlens.instrument.adapters.frameworks import LangGraphCallbackHandler

handler = LangGraphCallbackHandler(client=layerlens_client)

graph = StateGraph(state_schema=MyState)
# ... add nodes / edges ...
app = graph.compile()

app.invoke(initial_state, config={"callbacks": [handler]})
```

## Event surface

Inherits everything from the LangChain handler (`chain_start`/`chain_end`,
`llm_start`/`llm_end`, `tool_start`/`tool_end`) and adds:

- `agent.state` event per node transition with a SHA-256 hash of the
serialized graph state. Hashing is gated by ``emit_state_hash=True`` (the
default) and can be disabled if state is too large.
- `agent.handoff` event when one node hands control to another, derived from
the LangGraph node ID transition rather than message-content heuristics.

## Sample

[`samples/instrument/langgraph/example.py`](../../../samples/instrument/langgraph/example.py)

## Compat

- LangGraph 0.2+ (Pydantic v2-only)
- Python 3.9+
63 changes: 63 additions & 0 deletions docs/adapters/frameworks/semantic_kernel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Semantic Kernel adapter

Instruments [Semantic Kernel](https://github.com/microsoft/semantic-kernel)
kernels via the SK filter API (semantic-kernel ≥ 1.0).

## Install

```bash
pip install layerlens[semantic-kernel]
```

Pulls `semantic-kernel>=1.0.0`. Semantic Kernel requires Python 3.10+.

## Usage

```python
import asyncio
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from layerlens.instrument.adapters.frameworks import SemanticKernelAdapter

kernel = Kernel()
kernel.add_service(OpenAIChatCompletion(service_id="gpt4", ai_model_id="gpt-4o"))

adapter = SemanticKernelAdapter(client=layerlens_client)
adapter.connect(target=kernel) # registers filters on this Kernel

async def run():
return await kernel.invoke_prompt("Hello!")

asyncio.run(run())
adapter.disconnect() # removes filters and flushes the trace
```

`connect(target=kernel)` is required — the adapter installs filters on a
specific `Kernel` instance rather than monkey-patching a module.

## Event surface

The adapter registers three SK filters and emits flat events:

- `tool.call` from the function invocation filter — one event per plugin
function call with arguments and result.
- `prompt.render` from the prompt rendering filter — the rendered prompt
template with substituted variables.
- `tool.call` from the auto function invocation filter — LLM-initiated
function calls discovered during a chat completion.
- `model.invoke` from wrapped chat services on the kernel, including model
name and token usage when reported by the service.

Run boundaries are detected by a nesting depth counter: `_begin_run` fires
on the outermost function invocation and `_end_run` on its completion.
Concurrent invocations on different asyncio tasks are isolated via a
ContextVar-based `RunState`.

## Sample

[`samples/instrument/semantic_kernel/example.py`](../../../samples/instrument/semantic_kernel/example.py)

## Compat

- Semantic Kernel 1.0+
- Python 3.10+
Loading
Loading