Agents in Python code¶
Format id: python. Reads agents built with the OpenAI Agents SDK, LangChain and LangGraph prebuilt agents, Google ADK, and CrewAI, by static analysis.
$ marshal read path/to/agent/repo
$ marshal read path/to/agent/repo --format python
The code is never imported or run. Files are parsed with Python's ast module, and a small evaluator resolves only what can be known without running anything:
- literals, string concatenation, f-strings and
textwrap.dedent - names bound in the same function or module
- imports between the project's own modules, including relative imports and re-exports from
__init__.py - prompt files read with
Path(...).read_text()oropen(...).read(), when the file is inside the scanned directory
Everything else is recorded as unknown, with the reason: environment variables, function parameters, awaited values, conditional values.
What counts as an agent¶
Each call to an agent constructor is one agent.
| Framework | Platform id | Constructors |
|---|---|---|
| OpenAI Agents SDK | openai-agents |
agents.Agent (including Agent[Context](...)) |
| LangChain / LangGraph | langchain |
langchain.agents.create_agent, langgraph.prebuilt.create_react_agent |
| Google ADK | google-adk |
google.adk.agents.Agent, LlmAgent |
| CrewAI | crewai |
crewai.Agent, including agents configured from config/agents.yaml |
Agents without a framework¶
Code that calls a model API directly is also read. A model call that passes tools is an agent loop, so each such call site is recorded as an agent. Its record's detected_by says it was inferred, and extensions.python.inferred says why. A model call without tools is not an agent and is not recorded.
| API | Platform id | Calls |
|---|---|---|
| OpenAI (also Azure OpenAI, Databricks) | openai-api |
chat.completions.create, responses.create |
| Anthropic (also on Bedrock and Vertex) | anthropic-api |
messages.create, messages.stream, beta.messages.create |
| Google GenAI | google-genai |
models.generate_content |
| LiteLLM | litellm |
completion, acompletion |
| Bedrock Runtime | bedrock-runtime |
converse, converse_stream on a bedrock-runtime client |
From the call the reader takes the model, the provider (from the client class), the system prompt (system, instructions, a system or developer message, system_instruction), generation parameters, and the tools declared there: function schemas, hosted tools, MCP servers. Tool refs are tool/python.<project>/<module>:<tool name>, the same as a function of that name in the module. with_raw_response wrappers are recognized. When the client can't be resolved statically, because it's chosen at run time, the agent is still recorded from the SDK the module imports, with the provider unknown. Turn inference off with ReadOptions(infer_code_agents=False).
Parameters passed as **kwargs can't be seen statically, so such calls are not recorded. For code like that, commit an ADL file next to it.
Orchestration¶
Orchestration is reported as skipped, and its member agents are recorded: custom LangGraph StateGraphs, CrewAI Crews, and ADK SequentialAgent, ParallelAgent and LoopAgent.
The agent id is <directory name>/<agent name slug>. The name comes from the constructor's name (or CrewAI's role or config key), else from the variable or function the agent is assigned to or returned from.
Field mapping¶
| Ledger field | Source | Provenance |
|---|---|---|
display_name |
name; CrewAI role |
declared |
description |
OpenAI handoff_description; ADK description; CrewAI goal |
declared |
model.requested |
The model string or chat-model argument (ChatDatabricks(endpoint=...), LiteLlm(model=...), LLM(model=...)) |
declared |
model.provider |
The chat-model class, a provider:model or provider/model prefix, a Gemini model name, or the OpenAI SDK's default client (set_default_openai_client(AsyncDatabricksOpenAI()) → databricks) |
derived |
model.parameters |
ModelSettings, chat-model arguments, ADK GenerateContentConfig, CrewAI LLM |
declared |
instructions |
instructions, instruction, system_prompt, prompt |
declared |
f-strings with unresolved parts keep a {name} placeholder for each part |
derived | |
CrewAI: Role, Goal and Backstory, as CrewAI composes them |
derived | |
tools[] |
Decorated tool functions: name, description from the docstring's first paragraph, input schema from the signature (context parameters excluded) | declared / derived |
Hosted and built-in tools (WebSearchTool, google_search, ...) with their scope |
derived | |
MCP servers (MCPServerStreamableHttp, MCPToolset, DatabricksMCPServer, ...) as tool/mcp.<url or command> |
declared | |
UCFunctionToolkit(function_names=[...]) as tool/databricks.uc.function.<name>, the same refs as Databricks bundles |
declared | |
Other agents (handoffs, as_tool, AgentTool, ADK sub_agents) as agent/<agent id> |
declared | |
knowledge[] |
FileSearchTool vector stores; VectorSearchRetrieverTool indexes |
declared |
permissions.identity |
Unknown: set by the runtime the agent is deployed to | unknown |
extensions.python |
Framework, constructor, module, and arguments with no ledger field | declared |
Function tools get the ref tool/python.<project>/<module>:<function>, where the project is the directory the module names are relative to. Agents in the same project that share a function share the tool entity, while same-named functions in different projects (for example two copies of a template) stay distinct.
Linking to platform records¶
A Databricks App's source_code_path points at its agent code. The Databricks reader runs this reader on that directory. When it finds exactly one entry-point agent (one that no other agent calls), it adds that agent's model, instructions, tools and knowledge to the app's record, with basis pointing at the source line. The app's grants and identity still come from the bundle. Use ReadOptions(link_code=False) to read the bundle alone.
Known gaps¶
- Custom LangGraph graphs are not analyzed.
- ADK Agent Config (
root_agent.yaml) is not yet read. - Tool lists built at run time, such as tools fetched from an MCP client with
await client.get_tools(), are recorded undertools_unresolvedinextensions.python.