{
  "markdown": "# apx-agent\n\n[![CI](https://github.com/stuagano/apx-agent/actions/workflows/test.yml/badge.svg)](https://github.com/stuagano/apx-agent/actions/workflows/test.yml)\n[![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)\n[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)\n\nBuild governed Databricks agents. Write a Python object — apx-agent compiles it to whichever Databricks runtime you target.\n\n## LlmAgent — you control the loop\n\n`LlmAgent` (aliased as `Agent`) is an LLM + tools + a loop. You decide what it can call, when it stops, and what happens before and after each step.\n\n```python\nfrom apx_agent import LlmAgent, uc_function_tool, genie_tool\n\nagent = LlmAgent(\n    instructions=\"Investigate customer accounts.\",\n    tools=[\n        uc_function_tool(\"main.tools.lookup_account\"),\n        genie_tool(\"abc123\", description=\"Answer billing questions\"),\n    ],\n    max_iterations=10,\n    # memory=\"persistent\",   # durable semantic recall across sessions\n)\n```\n\nEvery hook is optional. None requires subclassing.\n\n```python\nfrom apx_agent import run_once\n\n# Invoke the agent (no HTTP request needed)\nresult = run_once(agent, \"Look up account 42.\")\nprint(result)\n```\n\n**Compose loops explicitly.** `LoopAgent` iterates until a condition is met; `SequentialAgent` pipelines agents in order; `ParallelAgent` fans out; `HandoffAgent` routes conversationally.\n\n```python\nfrom apx_agent import SequentialAgent\n\ninvestigation = SequentialAgent(\n    agents=[presence_check, lineage_trace, code_analysis, synthesis],\n    instructions=\"Investigate why data is missing.\",\n)\n```\n\nSee [docs/agents/composition.md](docs/agents/composition.md) for the full composition reference.\n\nThree agent types cover most use cases:\n\n| | |\n|---|---|\n| **`LlmAgent`** | The base. You own the loop: tools, hooks, guardrails, iteration cap. |\n| **`DataAgent`** | One line over a Unity Catalog schema. Grounded in real columns, runs as the calling user. |\n| **`CoworkerAgent`** | Joins two source systems on a shared key. Persona, join key, objective. |\n\nDeploy to Databricks Apps or Mosaic AI Model Serving — same agent definition, one flag changes the target.\n\n```bash\nuv add apx-agent\nuv run apx-agent doctor                          # check auth & environment first\nuv run apx-agent agents scaffold my-agent\ncd my-agent && uv sync\nuv run apx-agent agents deploy --target apps\n```\n\n`doctor` verifies your Databricks auth, tooling, and config before you scaffold. `scaffold` writes an editable `my-agent/` project directory containing the agent code and Apps bundle files. Run `deploy` from that directory; it bundles the project and prints the App URL when done. You can also deploy a hand-authored YAML spec by passing its path to `deploy`.\n\n---\n\n## What is apx-agent?\n\nBuilding agents on Databricks means dealing with a stack of systems that all speak different languages: LLM APIs have incompatible wire formats, memory backends have different interfaces, conversation history looks different depending on the framework, and trace schemas differ by SDK. Wiring all of that together correctly — and keeping it working as the stack evolves — is the problem nobody wants to have.\n\n**apx-agent is the normalization layer.** You declare what your agent should be. apx-agent makes it work and makes it observable, regardless of what's underneath.\n\n```toml\n[tool.apx.agent]\nname = \"payroll-coworker\"\nmodel = \"databricks/claude-3-7-sonnet\"\ninstructions = \"You are a payroll analyst...\"\n\n[tool.apx.agent.memory]\ntype = \"lakebase\"\nhost = \"${LAKEBASE_HOST}\"\ndatabase = \"payroll\"\ntable_name = \"main.payroll.agent_memory\"\nembedding_model = \"databricks-bge-large-en\"\nembedding_dim = 1024\n\n[tool.apx.agent.data]\ncatalog = \"main\"\nschema = \"payroll\"\n```\n\nThat declaration becomes: an agent grounded in its schema before the first question, durable memory that persists across sessions, a dev UI that surfaces tool calls and conversation correctly regardless of which underlying API format produced them, and a deployment target that can enforce Unity Catalog grants per caller when user authorization is configured.\n\n**What gets normalized so you don't have to think about it:**\n\n| Layer | What apx-agent hides |\n|---|---|\n| **LLM API format** | Responses API and chat-completions traces both surface identically in the dev UI |\n| **Conversation history** | One canonical message format across all agent types and frameworks |\n| **Memory backends** | Lakebase, UC-managed memory (Beta), or in-memory — same interface, declared not implemented |\n| **Observation** | Tool calls, spans, and conversation deltas normalized before they reach any renderer |\n| **Governance** | Identity passthrough, UC grants, and audit logging wired from the declaration |\n| **Multi-agent** | `sub_agents=[url]` + A2A — agents call each other across apps; supported tool/data calls can forward caller identity per hop |\n\nYou write a Python object or a TOML block. The normalization work is apx-agent's job.\n\n### The same agent, by hand vs. declared\n\nA typical \"build a support agent on Databricks\" notebook — ground it in Vector Search, wire two tools, run an agentic loop, trace it, log a served model — is about **220 lines** across the setup, the hand-authored tool schemas, the tool-calling loop, and a second copy of the tools-and-loop re-implemented inside a `PythonModel` for serving. apx-agent collapses that to a declaration plus the tool factories:\n\n| Step | By hand (raw SDK notebook) | apx-agent |\n|---|---|---|\n| **Ground** | `query_index(...)` call + manual row unpacking | `vector_search_tool(index, columns=..., num_results=...)` |\n| **Tools** | Two functions **+ hand-written OpenAI JSON schemas** | `vector_search_tool(...)`, `uc_function_tool(...)` — schemas introspected |\n| **Loop** | Hand-rolled `run_agent` — `max_turns`, `tool_call_id` bookkeeping, `model_dump(exclude_none=True)` | runtime-owned; you set `max_iterations` |\n| **Trace** | `@mlflow.trace` + `with mlflow.start_run(...)` wrappers | automatic |\n| **Ship** | ~90 lines: tools **and loop re-implemented** inside a `PythonModel`, temp `.py`, `infer_signature`, pinned `pip_requirements` | `apx-agent agents deploy --target apps` (or `serving`) |\n| **Govern** | tools run as the *notebook user* (`spark.table`) | tools run under the **calling user's** UC grants (OBO) |\n\nNet: **~220 lines → ~15 lines + a TOML block (~90% less code)** — and the deleted parts are the drift-prone ones. The raw notebook maintains the loop and both tools *twice* (once to demo, once inside the logged model); apx-agent serves the same object you ran locally. The one thing that doesn't shrink is the eval golden-set — that's real domain work, not boilerplate. See [docs/positioning.md](docs/positioning.md#by-hand-vs-declared-a-worked-comparison) for the full worked example.\n\n---\n\n## Quickstart\n\nPython 3.11+ required.\n\n**1. Install**\n\n```bash\nuv add apx-agent\n```\n\n**2. Scaffold an agent project**\n\n```bash\nuv run apx-agent agents scaffold my-agent\n```\n\nThe scaffold writes an editable `my-agent/` project in the current directory. It includes `agent.py`, `pyproject.toml`, `databricks.yml`, the generated Apps server, and the baked schema manifest when schema discovery succeeds.\n\n**3. Deploy the project**\n\n```bash\ncd my-agent && uv sync\nuv run apx-agent agents deploy --target apps\n```\n\n`deploy` bundles the current project and creates a Databricks App. It prints the URL when done. A hand-authored YAML spec can still be deployed by passing its path to `agents deploy`.\n\n**4. Run locally**\n\n```bash\nuv run apx-agent agents run --reload\n```\n\nFastAPI starts on `:8000`; chat at `/_apx/agent`, view traces at `/_apx/traces`, author new tools via the **New Tool** modal and inspect live tool schemas in the right panel of the Edit page (`/_apx/edit`) — the standalone `/_apx/tools` page is retired and redirects there. `agent.py` edits are picked up on restart — pass `--reload` (off by default) for auto-reload during local dev.\n\n> **Something not working?** Run `uv run apx-agent doctor` — checks Python, uv, Databricks CLI, auth, and project layout. Prints a `Fix:` line for anything wrong.\n\nSee [docs/get-started/quickstart.md](docs/get-started/quickstart.md) for the full walkthrough.\n\n### Know what you're pointed at\n\n`apx-agent status` prints the active Databricks profile and project/target — offline, no API call — so you can confirm context before you deploy:\n\n```bash\n$ apx-agent status\nprofile: fe-stable\nproject: payroll-coworker\ntarget:  apps\n```\n\n`--prompt` emits a compact one-liner (`apx:payroll-coworker(apps) ▸ fe-stable`). It's safe in an async/cached prompt segment (e.g. starship `[custom]`, powerlevel10k async), but the CLI cold-starts in ~1s, so don't call it on every render of a synchronous `PS1`. For an instant, zero-overhead prompt the same facts read straight from the shell:\n\n```bash\napx_ps1() {\n  local p=\"${DATABRICKS_CONFIG_PROFILE:-DEFAULT}\"\n  [ -f pyproject.toml ] && grep -q '\\[tool.apx.agent\\]' pyproject.toml && printf 'apx ▸ %s ' \"$p\"\n}\nsetopt PROMPT_SUBST 2>/dev/null; PROMPT='$(apx_ps1)'\"$PROMPT\"\n```\n\n---\n\n## DataAgent — one line over a UC schema\n\n```python\nfrom apx_agent import DataAgent\n\nagent = DataAgent(\"main\", \"sales\")\n```\n\nThat's a working agent. It knows the tables and columns in `main.sales` before the first question — no `SHOW TABLES` at runtime, no discovery prompt, no hallucinated schema.\n\nSchema discovery priority (first match wins):\n\n1. **Baked schema** — `.apx/schema.json`, written from the UC Tables API when the project is generated by `apx-agent agents scaffold`, or at `apx-agent agents deploy my-agent.yaml` time for a hand-authored spec. Ships with your code.\n2. **Live introspection** — pass `ws=WorkspaceClient()` for fresh schema at construction time.\n3. **Explicit override** — pass `tables={\"orders\": [\"id(bigint)\", ...]}` for tests.\n4. **Ungrounded fallback** — discovers schema with SQL on the first turn.\n\nTo update a scaffolded project after tables or columns change, run\n`apx-agent agents refresh-schema` from inside the project. It refreshes the\nlive metadata in place and preserves enriched OKF content by default; pass\n`--prune-missing-tables` only to intentionally remove concepts for tables no\nlonger present. Older projects can be converted with\n`apx-agent agents migrate-to-okf`. To seed an agent from Databricks' public\nindustry models, run\n`apx-agent agents ontology-jumpstart path/to/model.json --catalog <catalog> --schema <schema>`.\nSee the [DataAgent grounding asset\nlifecycle](docs/agents/data-agent.md#grounding-asset-lifecycle) for the\ncreate, jumpstart, refresh, enrich, and migrate workflow.\n\n```python\n# Live introspection\nfrom databricks.sdk import WorkspaceClient\nagent = DataAgent(\"main\", \"sales\", ws=WorkspaceClient())\n\n# Add persona, Genie space, vector search, or UC functions\nagent = DataAgent(\n    \"main\", \"sales\",\n    persona=\"a revenue analyst\",\n    genie_space=\"abc123\",\n    vector_index=\"main.sales.product_docs\",\n    extra_tools=[uc_function_tool(\"main.tools.send_alert\")],\n)\n```\n\n**Governance:** when user authorization is enabled and a valid per-request token is present, governed data and tool calls run under that caller's Unity Catalog grants. Background, M2M, and app-to-app calls still use their configured service-principal or gateway authorization path. See [docs/safety/identity-passthrough.md](docs/safety/identity-passthrough.md).\n\nSee [docs/agents/data-agent.md](docs/agents/data-agent.md) for the full reference.\n\n---\n\n## CoworkerAgent — join two source systems\n\nTwo source systems landed in a UC schema. One business entity links them. One question neither system can answer alone.\n\n```python\nfrom apx_agent import CoworkerAgent\n\nagent = CoworkerAgent(\n    \"main\", \"payroll\",\n    persona=\"a payroll operations analyst\",\n    join_key=\"employee ID\",\n    objective=\"surface mismatches between hours worked and paychecks issued\",\n    # memory=\"persistent\",  # remember facts across sessions\n)\n```\n\nThe `join_key` and `objective` are woven into the agent's grounded instructions. Common patterns:\n\n| Use case | System A | System B | Join key |\n|---|---|---|---|\n| Payroll reconciliation | Kronos (hours worked) | Workday (paychecks) | employee ID |\n| Quote-to-cash | Salesforce (deals) | NetSuite (invoices) | opportunity ID |\n| Onboarding / offboarding | Workday (employment) | Okta (access) | employee ID |\n| Warranty & entitlement | ServiceNow (cases) | SAP (contracts) | asset serial number |\n| Order status | Oracle ERP (orders) | TMS (freight) | PO / shipment number |\n| Claims integrity | Epic (chart) | Claims system (coding) | patient encounter |\n\n```bash\napx-agent agents scaffold my-coworker --template coworker   # writes an editable my-coworker/ project\n```\n\nSee [docs/agents/coworker.md](docs/agents/coworker.md) for the full reference.\n\n---\n\n## Many agents — a governed fleet\n\nWiring is tolerable for one agent. For a fleet — agents calling each other across\napps, each hop needing auth, discovery, and reachability — it's the whole job.\nThat's the wiring apx-agent deletes. One agent declares another and calls it:\n\n```python\n# Local: compose in one process\ninvestigation = SequentialAgent(agents=[presence, lineage, code, synthesis])\n\n# Remote: call a sibling agent in its own app, over A2A\nagent = Agent(\n    instructions=\"Route to the right specialist.\",\n    sub_agents=[\"$DATA_TRIAGE_URL\", \"$BILLING_URL\"],   # $VARs expand at startup\n)\n```\n\nWhen you split an agent into its own app, the sub-agent call goes through the\n**app-to-app auth path** — supported caller-token forwarding lets a downstream\nagent's tools run under the *asking user's* UC grants. App-to-app gateway\nauthorization and the callee's own model calls remain app-scoped; they do not\nautomatically become user-scoped. Every deployed agent serves an [A2A discovery\ncard](docs/multi-agent/a2a.md) at `/.well-known/agent.json`, so sibling apps find\neach other by probe, not by hardcoded config. `apx-agent doctor` reports whether\neach declared sub-agent is actually reachable.\n\nThis is the layer the platform leaves open. Databricks\n[Agent Services](https://docs.databricks.com/aws/en/ai-gateway/agent-services)\n(Beta) is a separate registration, discovery, and governance surface. Its\ncurrent documentation is transitional: one section describes `EXECUTE`\ninvocation while the limitations section says runtime invocation is unavailable.\nDo not assume that registering an agent creates a supported runtime call path;\napx-agent's A2A runtime remains separate.\n\nTwo examples ship this end-to-end:\n\n| Example | Multi-agent shape |\n|---|---|\n| **data-triage-agent** | 6-step `SequentialAgent` (local) delegating SQL + Delta forensics to a **data-inspector** sub-agent in its own app **over A2A** |\n| **customer_triage** | `HandoffAgent` over four specialists (triage / billing / account / technical) with principal-keyed memory recall surviving each handoff — Apps deploy verified live on `fe-stable` |\n\nPick the deploy boundary by lifecycle and consumers, not agent count — see\n[docs/multi-agent/overview.md](docs/multi-agent/overview.md).\n\n---\n\n## See what you built\n\nEvery deployed agent ships with the flow graph as a first-class runtime surface:\n`/_apx/topology` for people, `/_apx/topology/digest` for compact JSON, and an\nalways-on `get_agent_flow_graph` tool advertised through the agent card and MCP.\nThe card also includes a `flowGraph` block with the graph endpoints and tool name.\nThe graph shows agents, tools, sub-agents, and the UC / Genie / Vector Search /\nserving resources they reach. Click any node in the UI for its details.\n\n![/_apx/topology — interactive graph of agents, tools, sub-agents, and platform resources](docs/images/topology-customer-triage.png)\n\nSee [docs/get-started/dev-ui.md](docs/get-started/dev-ui.md) for the full `/_apx/*` surface: chat, traces, eval, tool authoring in the Edit page's New Tool modal, probe.\n\n---\n\n## Examples\n\n12 worked examples in [`python/examples/`](python/examples/EXAMPLES.md):\n\n| Example | What it shows |\n|---|---|\n| **customer_triage** | `HandoffAgent` + memory + UC tools |\n| **data-triage-agent** | 6-step `SequentialAgent` (presence → lineage → pipeline → genie → code → synthesis) |\n| **entity-resolution-agent** | Fuzzy account match via Vector Search + `HandoffAgent` |\n| **memory_demo** | `MemoryStore` + `ExampleStore` — recall across handoffs |\n| **slack-agent** | Slack-initiated runs as the Slack user's Databricks identity |\n| + 7 more | data-inspector, eligibility-agent, contract-parsing, shortage-intelligence, explain-my-bill, apx-builder, agent-hub |\n\n---\n\n## CLI\n\n```bash\napx-agent agents scaffold <name>   # writes an editable <name>/ project directory\napx-agent agents run               # local FastAPI dev server (/_apx/agent) — run inside a scaffolded project\napx-agent agents deploy            # deploy the current project to Databricks Apps\napx-agent eval run evalset.jsonl   # run against deployed endpoint with LLM judge\napx-agent traces list --agent <name>   # recent MLflow traces filtered by apx.* attributes\napx-agent fleet list --where team=revops   # bulk ops across many agents (tag/backfill/repoint; dry-run by default)\n                                   # repoint moves the @prod alias only (no rebuild); `fleet redeploy` is a deprecated alias\napx-agent label start --uc-name cat.sch.my_agent --judge domain_quality --scale 1-5 --assignee sme@co.com\n                                   # open SME labeling session → prints Review App URL + run-id\napx-agent label align --uc-name cat.sch.my_agent --judge domain_quality --run <run-id>\n                                   # align the judge on SME ratings (requires: pip install 'apx-agent[align]')\napx-agent doctor                   # diagnose auth, deps, project layout\n```\n\nSee [docs/get-started/cli.md](docs/get-started/cli.md) for the full surface.\n\n---\n\n## Docs\n\n| Topic | Doc |\n|---|---|\n| Quickstart | [docs/get-started/quickstart.md](docs/get-started/quickstart.md) |\n| Running agents (`run`, `stream`, `max_iterations`) | [docs/agents/llm-agent.md](docs/agents/llm-agent.md) |\n| DataAgent reference | [docs/agents/data-agent.md](docs/agents/data-agent.md) |\n| CoworkerAgent reference | [docs/agents/coworker.md](docs/agents/coworker.md) |\n| Agent composition | [docs/agents/composition.md](docs/agents/composition.md) |\n| Routing (RouterAgent, HandoffAgent) | [docs/agents/routing.md](docs/agents/routing.md) |\n| Tools — governed primitives | [docs/tools/overview.md](docs/tools/overview.md) |\n| Tools — custom (`@tool`, MCP) | [docs/tools/custom-tools.md](docs/tools/custom-tools.md) |\n| Multi-agent (sub-agents, A2A) | [docs/multi-agent/overview.md](docs/multi-agent/overview.md) |\n| Sessions + memory | [docs/running/sessions-and-memory.md](docs/running/sessions-and-memory.md) |\n| Guardrails and callbacks | [docs/safety/callbacks.md](docs/safety/callbacks.md) |\n| Identity passthrough + OBO | [docs/safety/identity-passthrough.md](docs/safety/identity-passthrough.md) |\n| Compliance (Watchdog, audit log) | [docs/safety/compliance.md](docs/safety/compliance.md) |\n| Deploy targets | [docs/deploy/apps-vs-model-serving.md](docs/deploy/apps-vs-model-serving.md) |\n| Scaffolded Apps CI/CD | [docs/deploy-cicd.md](docs/deploy-cicd.md) |\n| Upgrade apx-agent pins safely | [docs/upgrade.md](docs/upgrade.md) |\n| Evaluation | [docs/evaluate/overview.md](docs/evaluate/overview.md) |\n| Configuration (`pyproject.toml`) | [docs/reference/configuration.md](docs/reference/configuration.md) |\n| Coming from ADK or OpenAI Agents SDK | [docs/get-started/migration.md](docs/get-started/migration.md) |\n\n---\n\n## Coming from ADK or OpenAI Agents SDK?\n\nSee [docs/get-started/migration.md](docs/get-started/migration.md) for a concept-by-concept translation. The key mappings:\n\n| ADK / OpenAI | apx-agent |\n|---|---|\n| `Agent(name, instructions, model)` | `LlmAgent(name, instructions)` or `Agent(...)` — set the model via the `[tool.apx.agent]` `model` field in `pyproject.toml` |\n| `Runner.run()` | `run_once(agent, \"prompt\")` |\n| `@function_tool` / `@tool` | `@tool` |\n| `input_guardrails=[fn]` | `input_guardrails=[fn]` (same param name) |\n| `@input_guardrail` tripwire | raise `PermissionError` in `before_agent_callback` |\n| `before_tool_callback` | `before_tool` or `before_tool_callback` (both accepted) |\n| `MemoryService` | `MemoryStore` |\n| Handoffs | `HandoffAgent` |\n\n---\n\n## For AI coding assistants\n\nThe repo ships an [`llms.txt`](llms.txt) index of all documentation URLs. Add the docs as a local MCP server in Claude Code:\n\n```bash\nclaude mcp add apx-agent-docs --transport stdio -- \\\n  uvx --from mcpdoc mcpdoc \\\n  --urls \"apxAgent:https://raw.githubusercontent.com/stuagano/apx-agent/main/llms.txt\" \\\n  --transport stdio\n```\n\n---\n\n## License\n\nApache 2.0 — see [LICENSE](LICENSE).\n",
  "bytes": 20821,
  "sha": "9bde548c7398b7aeb1619716723afbfc31f710675a704bd3f457fcaced7f924f",
  "repo_slug": "stuagano/apx-agent",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_stuagano_apx_agent_python_payroll_cowork_5d789217/readme"
}