Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions docs/adapters/frameworks-agentforce.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Salesforce Agentforce framework adapter

`layerlens.instrument.adapters.frameworks.agentforce.AgentForceAdapter`
imports Salesforce Agentforce session traces from Data Cloud DMOs and emits
them as LayerLens events.

This adapter is **import-mode** rather than runtime-instrumentation: it
authenticates against a Salesforce org via OAuth 2.0 JWT Bearer and runs
SOQL queries against the AgentForce DMO objects to backfill trace data.

## Install

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

Pulls `requests>=2.28`. The Salesforce credentials must be provisioned
out-of-band (Connected App + private key + permitted user).

## Quick start

```python
from layerlens.instrument.adapters.frameworks.agentforce import (
AgentForceAdapter,
SalesforceCredentials,
)
from layerlens.instrument.transport.sink_http import HttpEventSink

credentials = SalesforceCredentials(
client_id="3MVG9...",
username="agent-importer@example.com",
private_key="env:SALESFORCE_PRIVATE_KEY", # or file path or raw PEM
instance_url="https://example.my.salesforce.com",
)

sink = HttpEventSink(adapter_name="salesforce_agentforce")
adapter = AgentForceAdapter(credentials=credentials)
adapter.add_sink(sink)
adapter.connect() # JWT flow runs here

result = adapter.import_sessions(
start_date="2026-04-01",
end_date="2026-04-25",
limit=100,
)
print(f"Imported {result.events_generated} events from {result.sessions_imported} sessions")

adapter.disconnect()
sink.close()
```

## What's wrapped

This adapter does not monkey-patch anything. It calls SOQL against:

- `AIAgentSession` — top-level session record
- `AIAgentSessionParticipant` — agents + users in the session
- `AIAgentInteraction` — turns within the session
- `AIAgentInteractionStep` — individual steps inside an interaction
- `AIAgentInteractionMessage` — raw input/output messages

Each row is normalized via `AgentForceNormalizer` and emitted through the
adapter's `emit_dict_event` pipeline.

Companion modules:

- `AgentApiClient` — direct REST client for Agent API (real-time capture)
- `PlatformEventSubscriber` — gRPC Pub/Sub subscriber for near-real-time
- `TrustLayerImporter` — imports Einstein Trust Layer policies
- `EinsteinEvaluator` — runs LLM evaluation scenarios

## Events emitted

| Event | Layer | When |
|---|---|---|
| `agent.input` | L1 | Per `AIAgentInteractionMessage` with role=user. |
| `agent.output` | L1 | Per `AIAgentInteractionMessage` with role=agent. |
| `agent.action` | L4a | Per `AIAgentInteractionStep`. |
| `tool.call` | L5a | Per step where `StepType` is a tool/action invocation. |
| `model.invoke` | L3 | Per LLM call captured in step metadata. |
| `policy.violation` | cross-cutting | Per Einstein Trust Layer policy hit. |
| `agent.handoff` | L4a | Per `AIAgentSessionParticipant` change. |

Each emitted event includes `_identity` (the Salesforce record `Id`) and
`_timestamp` (record `LastModifiedDate`) for re-import idempotency.

## Salesforce specifics

- **Authentication**: JWT Bearer (OAuth 2.0). `SalesforceCredentials` accepts
the private key as a raw PEM, an `env:NAME` reference, or a file path.
- **Token lifetime**: ~2 hours. The adapter re-authenticates automatically
when the cached token expires before the next operation.
- **Rate limits**: a warning is logged when the API daily limit consumption
passes 80%. Salesforce returns the consumption in response headers.
- **Incremental sync**: pass `last_import_timestamp` to
`import_sessions(...)` to fetch only records modified since a watermark.
- **Batch size**: configurable via the `batch_size` constructor arg
(default 200, the SOQL maximum is 2000).

## Capture config

```python
from layerlens.instrument.adapters._base import CaptureConfig

# Recommended for compliance backfills.
adapter = AgentForceAdapter(
credentials=credentials,
capture_config=CaptureConfig.standard(),
)

# Strip raw message bodies, keep only structural events.
adapter = AgentForceAdapter(
credentials=credentials,
capture_config=CaptureConfig(
l1_agent_io=True,
l4a_environment_config=True,
capture_content=False,
),
)
```

## BYOK

Salesforce manages its own model keys (Einstein Trust Layer abstracts the
provider). The adapter does not own model API keys. The Salesforce
credentials themselves are intended to live in atlas-app's `byok_credentials`
table once M1.B ships — see `docs/adapters/byok.md`.

## Replay

`adapter.serialize_for_replay()` returns a `ReplayableTrace` with all events
captured during the current `import_sessions` call. Replay is a re-emit
operation: the adapter does not re-query Salesforce.
111 changes: 111 additions & 0 deletions docs/adapters/frameworks-autogen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# AutoGen framework adapter

`layerlens.instrument.adapters.frameworks.autogen.AutoGenAdapter` instruments
[Microsoft AutoGen](https://github.com/microsoft/autogen) `ConversableAgent`
objects, capturing message exchange, LLM calls, code execution, and group-chat
turns.

## Install

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

Pulls `pyautogen>=0.2,<0.5`.

## Quick start

```python
from autogen import AssistantAgent, UserProxyAgent

from layerlens.instrument.adapters.frameworks.autogen import (
AutoGenAdapter,
instrument_agents,
)
from layerlens.instrument.transport.sink_http import HttpEventSink

sink = HttpEventSink(adapter_name="autogen")
adapter = AutoGenAdapter()
adapter.add_sink(sink)
adapter.connect()

assistant = AssistantAgent(name="assistant", llm_config={"model": "gpt-4o-mini"})
user = UserProxyAgent(name="user", human_input_mode="NEVER", code_execution_config=False)

adapter.connect_agents(assistant, user)
user.initiate_chat(assistant, message="What is 2+2?", max_turns=1)

adapter.disconnect()
sink.close()
```

`instrument_agents(*agents)` is the one-line equivalent of the three-line
adapter setup above.

## What's wrapped

`adapter.connect_agents(*agents)` monkey-patches the following on each
`ConversableAgent`:

- `send` — emits `agent.input` for the outgoing message.
- `receive` — emits `agent.output` for the incoming message.
- `generate_reply` — emits `model.invoke` and any `tool.call` events.
- `execute_code_blocks` — emits `agent.code` and `tool.call` for code
execution (when present).

The originals are stashed on the adapter and restored on `disconnect()`.
A `GroupChatTracer` wires similar hooks onto `GroupChatManager`, and a
`HumanProxyTracer` adds `agent.handoff` semantics for human-in-the-loop
proxies.

## Events emitted

| Event | Layer | When |
|---|---|---|
| `environment.config` | L4a | First time each agent is seen. |
| `agent.input` | L1 | Every `send`. |
| `agent.output` | L1 | Every `receive`. |
| `agent.action` | L4a | Per `generate_reply` decision. |
| `agent.code` | L2 | When `execute_code_blocks` runs and `l2_agent_code` is enabled. |
| `agent.handoff` | L4a | Group-chat speaker selection / human handoff. |
| `agent.state.change` | cross-cutting | Conversation history mutations. |
| `tool.call` | L5a | Per function-call inside `generate_reply`. |
| `model.invoke` | L3 | Per LLM call. |

## AutoGen specifics

- **Multi-agent attribution**: `agent_name`, `recipient_name`, and
`message_seq` (a monotonic counter) are included on every event so the
full chat can be reconstructed in order.
- **Group chats**: `GroupChatTracer` registers as a callback on
`GroupChatManager`, capturing the speaker-selection turns. Pass a
`GroupChatManager` to `connect_agents` alongside the participants.
- **Code execution**: when an agent runs code blocks, the language and
truncated code body emit `agent.code` (only if
`CaptureConfig.l2_agent_code` is enabled).

## Capture config

```python
from layerlens.instrument.adapters._base import CaptureConfig

# Recommended.
adapter = AutoGenAdapter(capture_config=CaptureConfig.standard())

# Production-light: skip the verbose code-execution events.
adapter = AutoGenAdapter(
capture_config=CaptureConfig(
l1_agent_io=True,
l3_model_metadata=True,
l4a_environment_config=True,
l5a_tool_calls=True,
l2_agent_code=False,
),
)
```

## BYOK

AutoGen reads its `llm_config` to instantiate provider clients. The adapter
does not own those keys. For platform-managed BYOK see
`docs/adapters/byok.md` (atlas-app M1.B).
122 changes: 122 additions & 0 deletions docs/adapters/frameworks-crewai.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# CrewAI framework adapter

`layerlens.instrument.adapters.frameworks.crewai.CrewAIAdapter` instruments
[CrewAI](https://github.com/joaomdmoura/crewai) crews — multi-agent
collaborations with explicit role assignments and task delegation.

## Install

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

Pulls `crewai>=0.30,<0.90`.

## Quick start

```python
from crewai import Agent, Crew, Task

from layerlens.instrument.adapters.frameworks.crewai import (
CrewAIAdapter,
instrument_crew,
)
from layerlens.instrument.transport.sink_http import HttpEventSink

sink = HttpEventSink(adapter_name="crewai")
adapter = CrewAIAdapter()
adapter.add_sink(sink)
adapter.connect()

# build a one-task crew
researcher = Agent(role="Researcher", goal="Answer", backstory="...")
task = Task(description="What is 2 + 2?", agent=researcher, expected_output="A number")
crew = Crew(agents=[researcher], tasks=[task])

instrumented = adapter.instrument_crew(crew)
result = instrumented.kickoff()

adapter.disconnect()
sink.close()
```

The `instrument_crew(crew)` convenience helper wraps the whole flow above:

```python
instrumented = instrument_crew(crew) # connects + wraps in one call
result = instrumented.kickoff()
```

## What's wrapped

`adapter.instrument_crew(crew)` patches the following on the underlying
crew + agent objects:

- `Crew.kickoff` — emits `agent.input` + `environment.config` at start,
`agent.output` at completion.
- `Agent.execute_task` — emits `agent.action` + `agent.code` (when enabled)
per task.
- `Agent._invoke_tool` — emits `tool.call` per tool invocation.
- `Agent.llm.call` — emits `model.invoke` per LLM call.
- Crew delegation events via `CrewDelegationTracker` — emits `agent.handoff`
on `Agent.delegate` calls.

## Events emitted

| Event | Layer | When |
|---|---|---|
| `environment.config` | L4a | First `kickoff` of an instrumented crew. |
| `agent.input` | L1 | Start of every `kickoff`. |
| `agent.output` | L1 | End of every `kickoff` and per-task. |
| `agent.code` | L2 | When `CaptureConfig.l2_agent_code` is true; one per agent. |
| `agent.action` | L4a | Per task execution. |
| `agent.state.change` | cross-cutting | When agent memory or context changes. |
| `agent.handoff` | L4a | When one agent delegates to another. |
| `tool.call` | L5a | Per tool invocation inside a task. |
| `model.invoke` | L3 | Per LLM call from any crew agent. |

## CrewAI specifics

- **Multi-agent attribution**: every event payload includes `agent_id`,
`agent_role`, and (when present) `task_id` so the platform can reconstruct
who-did-what across a crew.
- **Memory tracking**: when a `memory_service` is passed to
`CrewAIAdapter(memory_service=...)`, agent short-term memory writes emit
`agent.state.change` with the memory diff.
- **Sequential vs hierarchical**: works for both `Process.sequential` and
`Process.hierarchical`. Hierarchical delegation is captured via the
delegation tracker.

## Capture config

```python
from layerlens.instrument.adapters._base import CaptureConfig

# Recommended.
adapter = CrewAIAdapter(capture_config=CaptureConfig.standard())

# Add agent.code (the prompt template / system message of each agent).
adapter = CrewAIAdapter(
capture_config=CaptureConfig(
l1_agent_io=True,
l2_agent_code=True,
l3_model_metadata=True,
l5a_tool_calls=True,
),
)
```

## BYOK

CrewAI agents instantiate their own LLM clients (LangChain or LiteLLM under
the hood). The CrewAI adapter does not own those keys. For platform-managed
BYOK see `docs/adapters/byok.md` (atlas-app M1.B).

## Backward compatibility

```python
from layerlens.instrument.adapters.frameworks.crewai import STRATIXCrewCallback
```

`STRATIXCrewCallback` is an alias for `LayerLensCrewCallback` and will be
removed in v2.0.
Loading