ariadne-agent 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ariadne_agent-0.2.0/PKG-INFO +134 -0
- ariadne_agent-0.2.0/README.md +124 -0
- ariadne_agent-0.2.0/pyproject.toml +58 -0
- ariadne_agent-0.2.0/setup.cfg +4 -0
- ariadne_agent-0.2.0/src/ariadne_agent/__init__.py +32 -0
- ariadne_agent-0.2.0/src/ariadne_agent/analyst.py +302 -0
- ariadne_agent-0.2.0/src/ariadne_agent/authoring.py +480 -0
- ariadne_agent-0.2.0/src/ariadne_agent/mcp_tools.py +319 -0
- ariadne_agent-0.2.0/src/ariadne_agent/session.py +467 -0
- ariadne_agent-0.2.0/src/ariadne_agent/skills.py +140 -0
- ariadne_agent-0.2.0/src/ariadne_agent.egg-info/PKG-INFO +134 -0
- ariadne_agent-0.2.0/src/ariadne_agent.egg-info/SOURCES.txt +16 -0
- ariadne_agent-0.2.0/src/ariadne_agent.egg-info/dependency_links.txt +1 -0
- ariadne_agent-0.2.0/src/ariadne_agent.egg-info/requires.txt +3 -0
- ariadne_agent-0.2.0/src/ariadne_agent.egg-info/top_level.txt +1 -0
- ariadne_agent-0.2.0/tests/test_analyst.py +902 -0
- ariadne_agent-0.2.0/tests/test_authoring.py +1169 -0
- ariadne_agent-0.2.0/tests/test_mcp_roles.py +593 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ariadne-agent
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Installable Ariadne Agent backend: fixed-role conversations over native Ariadne tools
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: ariadne-data>=0.2.0
|
|
8
|
+
Requires-Dist: tau-ai==0.4.6
|
|
9
|
+
Requires-Dist: tau-mcp>=0.1.0
|
|
10
|
+
|
|
11
|
+
# ariadne-agent
|
|
12
|
+
|
|
13
|
+
The installable Ariadne Agent backend: a fixed-role, in-memory conversation
|
|
14
|
+
over **native Ariadne Python tools** — no CLI, no shell, no server, no raw SQL
|
|
15
|
+
([spec 2026-09-29-0006](../project/specs/2026-09-29-0006-ariadne-agent-backend.md),
|
|
16
|
+
[ADR-0014](../project/adr/0014-embedded-tau-agent-backend.md), issue #23).
|
|
17
|
+
This revision ships the **Analyst** and **Authoring** roles plus optionally
|
|
18
|
+
configured **role-restricted stdio MCP** tools (issue #25).
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
Depends on `ariadne-data` (never the unrelated PyPI `ariadne` GraphQL project),
|
|
23
|
+
the verified Tau release `tau-ai==0.4.6`, whose embeddable
|
|
24
|
+
`tau_agent.AgentHarness` / `tau_ai.OpenAICompatibleProvider` import paths this
|
|
25
|
+
package uses, and the owned `tau-mcp` embeddable stdio MCP core. The `tau-ai`
|
|
26
|
+
wheel also installs `tau_coding`'s files, but this package never imports or
|
|
27
|
+
invokes `tau_coding` at runtime (the `tau_mcp.proxy` module is likewise not
|
|
28
|
+
imported here because it is the Tau-extension flavor), and never launches a
|
|
29
|
+
server — installing `ariadne-data` alone does not install the agent.
|
|
30
|
+
|
|
31
|
+
Local development resolves `ariadne-data` from the repository checkout through
|
|
32
|
+
`[tool.uv.sources]` in `pyproject.toml` (non-published tool configuration
|
|
33
|
+
only; the published dependency metadata names the distribution); `tau-mcp`
|
|
34
|
+
resolves the same way from its own repository checkout:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd ariadne-agent && uv run pytest -q tests/test_analyst.py
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Use
|
|
41
|
+
|
|
42
|
+
The host selects everything up front — project root, role, model, and a fully
|
|
43
|
+
configured provider (model identity, endpoint and credentials are constructed
|
|
44
|
+
outside user messages and data-project artifacts; the agent loads no global
|
|
45
|
+
provider or MCP configuration):
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from ariadne_agent import AssistantText, ToolActivity, open_session
|
|
49
|
+
from tau_ai import OpenAICompatibleProvider, OpenAICompatibleConfig
|
|
50
|
+
|
|
51
|
+
provider = OpenAICompatibleProvider(OpenAICompatibleConfig(api_key=..., base_url=...))
|
|
52
|
+
session = open_session(role="analyst", project="/path/to/project",
|
|
53
|
+
model="your-model", provider=provider)
|
|
54
|
+
async for event in session.send("gross margin by region?"):
|
|
55
|
+
match event:
|
|
56
|
+
case AssistantText(text=text): print(text)
|
|
57
|
+
case ToolActivity(tool=tool): print(f"[tool] {tool}")
|
|
58
|
+
session.cancel() # cooperative: ends subsequent model turns
|
|
59
|
+
session.close()
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## MCP (optional, role-restricted)
|
|
63
|
+
|
|
64
|
+
Passing `mcp_settings` attaches ONE restricted MCP registry to that session,
|
|
65
|
+
built from host-supplied configuration only — validated stdio server entries,
|
|
66
|
+
a cache root, and each role's operator-approved qualified tool names. No
|
|
67
|
+
`~/.tau/mcp.json` or any other ambient MCP configuration is ever loaded, and a
|
|
68
|
+
model request can never alter server commands, the allowlist, or credentials:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from ariadne_agent.mcp_tools import McpSettings
|
|
72
|
+
from tau_mcp.config import ServerConfig
|
|
73
|
+
|
|
74
|
+
mcp = McpSettings(
|
|
75
|
+
servers={"weather": ServerConfig(name="weather", command="...", args=(...))},
|
|
76
|
+
cache_root=Path("/var/cache/ariadne-mcp"),
|
|
77
|
+
role_tools={"analyst": ("weather__lookup",)}, # authoring absent => no MCP tools
|
|
78
|
+
)
|
|
79
|
+
session = open_session(role="analyst", ..., mcp_settings=mcp)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Permitted tools surface to the model as one `mcp` proxy tool (search and call
|
|
83
|
+
by `server__tool` name); direct MCP registration stays a Tau extension and is
|
|
84
|
+
not offered. The registry's frozen per-instance allowlist restricts discovery
|
|
85
|
+
**and** dispatch, so a forbidden name the model supplies through the proxy is
|
|
86
|
+
rejected before any spawn or call — the server never sees it. `close()` reaps
|
|
87
|
+
every MCP child this session spawned; a call already executing finishes or
|
|
88
|
+
times out under the MCP client's bounded request timeout (no immediate
|
|
89
|
+
interruption is promised). Omit `mcp_settings` and the native tool surface is
|
|
90
|
+
exactly as before.
|
|
91
|
+
|
|
92
|
+
The project root selects one of the two runtime kinds (issue #38/G6): a
|
|
93
|
+
**managed Git data project** — one carrying Ariadne's `.ariadne/` operator
|
|
94
|
+
state — resolves through `QueryRuntime.open_project` and serves its pinned
|
|
95
|
+
human-admitted snapshot (a missing admission is an explicit error naming the
|
|
96
|
+
human `ariadne-admit` step, never a fallback to mutable files), while an
|
|
97
|
+
**explicit legacy artifact-root project** must contain the served
|
|
98
|
+
`semantic_layer/` and `context_layer/` directories and either a source
|
|
99
|
+
registry (`ariadne.yaml`) or a `demo.db`; every path resolves against that
|
|
100
|
+
root, never the ambient working directory. The legacy → new mapping lives in
|
|
101
|
+
[docs/how-to/git-authoring.md](../docs/how-to/git-authoring.md).
|
|
102
|
+
|
|
103
|
+
## Contract
|
|
104
|
+
|
|
105
|
+
- **Typed async events**: assistant text, tool activity (verbatim JSON payload,
|
|
106
|
+
error flag), session errors, and run completion. Harness-internal event
|
|
107
|
+
types never leak through.
|
|
108
|
+
- **One active run per session**; `send` while running raises. **Fixed**
|
|
109
|
+
role/project/model — a different one requires a new session, with no silent
|
|
110
|
+
transcript or tool-access transfer.
|
|
111
|
+
- **In memory only**: a process restart loses the messages; there is no resume
|
|
112
|
+
operation, deliberately.
|
|
113
|
+
- **Analyst tools** are exactly `ariadne.runtime.AriadneRuntime`'s six public
|
|
114
|
+
operations (`list_metrics`, `search_context`, `get_metric`, `list_datasets`,
|
|
115
|
+
`define_metric`, `resolve_term`) as thin native tools: values, Policy text
|
|
116
|
+
and citations, paths, and `governed` labels pass through verbatim. Term
|
|
117
|
+
resolution is read-only admitted-context guidance — an alias never
|
|
118
|
+
authorizes a join. No source approval, no promotion, no filesystem writes
|
|
119
|
+
beyond the runtime's own staging.
|
|
120
|
+
- **Authoring tools** mirror `AuthoringProject`'s Git contract one-to-one —
|
|
121
|
+
`start_proposal`, `read_artifact`, `edit_proposal`, `propose_sources`,
|
|
122
|
+
`inputs`, `upload_document`, `validate_proposal`, `refresh_schema`,
|
|
123
|
+
`inspect_mapping`, `resolve_term`, `review_proposal` — with automatic
|
|
124
|
+
labeled-hypothesis saves through `edit_proposal` (no per-save prompt) and
|
|
125
|
+
restart-readable Git/evidence state. Source approval, admission,
|
|
126
|
+
reconciliation, legacy import/enrichment and Promotion are deliberately
|
|
127
|
+
absent: human-only, outside the agent.
|
|
128
|
+
- **Guidance**: the version-matched packaged `ariadne/skill_assets/SKILL.md`
|
|
129
|
+
from the installed `ariadne-data`, plus a short native-tool mapping. No
|
|
130
|
+
global skill scans, no model-invocable skill scripts.
|
|
131
|
+
- **Missing promoted catalog**: opening an Analyst session fails fast with an
|
|
132
|
+
actionable error naming the human step — Promotion for a legacy project,
|
|
133
|
+
`ariadne-admit` for a managed Git project. The failure is session-local — it
|
|
134
|
+
cannot block a later (e.g. Authoring) session.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# ariadne-agent
|
|
2
|
+
|
|
3
|
+
The installable Ariadne Agent backend: a fixed-role, in-memory conversation
|
|
4
|
+
over **native Ariadne Python tools** — no CLI, no shell, no server, no raw SQL
|
|
5
|
+
([spec 2026-09-29-0006](../project/specs/2026-09-29-0006-ariadne-agent-backend.md),
|
|
6
|
+
[ADR-0014](../project/adr/0014-embedded-tau-agent-backend.md), issue #23).
|
|
7
|
+
This revision ships the **Analyst** and **Authoring** roles plus optionally
|
|
8
|
+
configured **role-restricted stdio MCP** tools (issue #25).
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
Depends on `ariadne-data` (never the unrelated PyPI `ariadne` GraphQL project),
|
|
13
|
+
the verified Tau release `tau-ai==0.4.6`, whose embeddable
|
|
14
|
+
`tau_agent.AgentHarness` / `tau_ai.OpenAICompatibleProvider` import paths this
|
|
15
|
+
package uses, and the owned `tau-mcp` embeddable stdio MCP core. The `tau-ai`
|
|
16
|
+
wheel also installs `tau_coding`'s files, but this package never imports or
|
|
17
|
+
invokes `tau_coding` at runtime (the `tau_mcp.proxy` module is likewise not
|
|
18
|
+
imported here because it is the Tau-extension flavor), and never launches a
|
|
19
|
+
server — installing `ariadne-data` alone does not install the agent.
|
|
20
|
+
|
|
21
|
+
Local development resolves `ariadne-data` from the repository checkout through
|
|
22
|
+
`[tool.uv.sources]` in `pyproject.toml` (non-published tool configuration
|
|
23
|
+
only; the published dependency metadata names the distribution); `tau-mcp`
|
|
24
|
+
resolves the same way from its own repository checkout:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cd ariadne-agent && uv run pytest -q tests/test_analyst.py
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Use
|
|
31
|
+
|
|
32
|
+
The host selects everything up front — project root, role, model, and a fully
|
|
33
|
+
configured provider (model identity, endpoint and credentials are constructed
|
|
34
|
+
outside user messages and data-project artifacts; the agent loads no global
|
|
35
|
+
provider or MCP configuration):
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from ariadne_agent import AssistantText, ToolActivity, open_session
|
|
39
|
+
from tau_ai import OpenAICompatibleProvider, OpenAICompatibleConfig
|
|
40
|
+
|
|
41
|
+
provider = OpenAICompatibleProvider(OpenAICompatibleConfig(api_key=..., base_url=...))
|
|
42
|
+
session = open_session(role="analyst", project="/path/to/project",
|
|
43
|
+
model="your-model", provider=provider)
|
|
44
|
+
async for event in session.send("gross margin by region?"):
|
|
45
|
+
match event:
|
|
46
|
+
case AssistantText(text=text): print(text)
|
|
47
|
+
case ToolActivity(tool=tool): print(f"[tool] {tool}")
|
|
48
|
+
session.cancel() # cooperative: ends subsequent model turns
|
|
49
|
+
session.close()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## MCP (optional, role-restricted)
|
|
53
|
+
|
|
54
|
+
Passing `mcp_settings` attaches ONE restricted MCP registry to that session,
|
|
55
|
+
built from host-supplied configuration only — validated stdio server entries,
|
|
56
|
+
a cache root, and each role's operator-approved qualified tool names. No
|
|
57
|
+
`~/.tau/mcp.json` or any other ambient MCP configuration is ever loaded, and a
|
|
58
|
+
model request can never alter server commands, the allowlist, or credentials:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from ariadne_agent.mcp_tools import McpSettings
|
|
62
|
+
from tau_mcp.config import ServerConfig
|
|
63
|
+
|
|
64
|
+
mcp = McpSettings(
|
|
65
|
+
servers={"weather": ServerConfig(name="weather", command="...", args=(...))},
|
|
66
|
+
cache_root=Path("/var/cache/ariadne-mcp"),
|
|
67
|
+
role_tools={"analyst": ("weather__lookup",)}, # authoring absent => no MCP tools
|
|
68
|
+
)
|
|
69
|
+
session = open_session(role="analyst", ..., mcp_settings=mcp)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Permitted tools surface to the model as one `mcp` proxy tool (search and call
|
|
73
|
+
by `server__tool` name); direct MCP registration stays a Tau extension and is
|
|
74
|
+
not offered. The registry's frozen per-instance allowlist restricts discovery
|
|
75
|
+
**and** dispatch, so a forbidden name the model supplies through the proxy is
|
|
76
|
+
rejected before any spawn or call — the server never sees it. `close()` reaps
|
|
77
|
+
every MCP child this session spawned; a call already executing finishes or
|
|
78
|
+
times out under the MCP client's bounded request timeout (no immediate
|
|
79
|
+
interruption is promised). Omit `mcp_settings` and the native tool surface is
|
|
80
|
+
exactly as before.
|
|
81
|
+
|
|
82
|
+
The project root selects one of the two runtime kinds (issue #38/G6): a
|
|
83
|
+
**managed Git data project** — one carrying Ariadne's `.ariadne/` operator
|
|
84
|
+
state — resolves through `QueryRuntime.open_project` and serves its pinned
|
|
85
|
+
human-admitted snapshot (a missing admission is an explicit error naming the
|
|
86
|
+
human `ariadne-admit` step, never a fallback to mutable files), while an
|
|
87
|
+
**explicit legacy artifact-root project** must contain the served
|
|
88
|
+
`semantic_layer/` and `context_layer/` directories and either a source
|
|
89
|
+
registry (`ariadne.yaml`) or a `demo.db`; every path resolves against that
|
|
90
|
+
root, never the ambient working directory. The legacy → new mapping lives in
|
|
91
|
+
[docs/how-to/git-authoring.md](../docs/how-to/git-authoring.md).
|
|
92
|
+
|
|
93
|
+
## Contract
|
|
94
|
+
|
|
95
|
+
- **Typed async events**: assistant text, tool activity (verbatim JSON payload,
|
|
96
|
+
error flag), session errors, and run completion. Harness-internal event
|
|
97
|
+
types never leak through.
|
|
98
|
+
- **One active run per session**; `send` while running raises. **Fixed**
|
|
99
|
+
role/project/model — a different one requires a new session, with no silent
|
|
100
|
+
transcript or tool-access transfer.
|
|
101
|
+
- **In memory only**: a process restart loses the messages; there is no resume
|
|
102
|
+
operation, deliberately.
|
|
103
|
+
- **Analyst tools** are exactly `ariadne.runtime.AriadneRuntime`'s six public
|
|
104
|
+
operations (`list_metrics`, `search_context`, `get_metric`, `list_datasets`,
|
|
105
|
+
`define_metric`, `resolve_term`) as thin native tools: values, Policy text
|
|
106
|
+
and citations, paths, and `governed` labels pass through verbatim. Term
|
|
107
|
+
resolution is read-only admitted-context guidance — an alias never
|
|
108
|
+
authorizes a join. No source approval, no promotion, no filesystem writes
|
|
109
|
+
beyond the runtime's own staging.
|
|
110
|
+
- **Authoring tools** mirror `AuthoringProject`'s Git contract one-to-one —
|
|
111
|
+
`start_proposal`, `read_artifact`, `edit_proposal`, `propose_sources`,
|
|
112
|
+
`inputs`, `upload_document`, `validate_proposal`, `refresh_schema`,
|
|
113
|
+
`inspect_mapping`, `resolve_term`, `review_proposal` — with automatic
|
|
114
|
+
labeled-hypothesis saves through `edit_proposal` (no per-save prompt) and
|
|
115
|
+
restart-readable Git/evidence state. Source approval, admission,
|
|
116
|
+
reconciliation, legacy import/enrichment and Promotion are deliberately
|
|
117
|
+
absent: human-only, outside the agent.
|
|
118
|
+
- **Guidance**: the version-matched packaged `ariadne/skill_assets/SKILL.md`
|
|
119
|
+
from the installed `ariadne-data`, plus a short native-tool mapping. No
|
|
120
|
+
global skill scans, no model-invocable skill scripts.
|
|
121
|
+
- **Missing promoted catalog**: opening an Analyst session fails fast with an
|
|
122
|
+
actionable error naming the human step — Promotion for a legacy project,
|
|
123
|
+
`ariadne-admit` for a managed Git project. The failure is session-local — it
|
|
124
|
+
cannot block a later (e.g. Authoring) session.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ariadne-agent"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Installable Ariadne Agent backend: fixed-role conversations over native Ariadne tools"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
dependencies = [
|
|
12
|
+
# Never bare `ariadne` — that is the unrelated PyPI GraphQL project (ADR-0014).
|
|
13
|
+
"ariadne-data>=0.2.0",
|
|
14
|
+
# The release whose embeddable tau_agent.AgentHarness / tau_ai.OpenAICompatibleProvider
|
|
15
|
+
# import paths were verified for this package (ADR-0014: pin what was tested).
|
|
16
|
+
"tau-ai==0.4.6",
|
|
17
|
+
# The owned embeddable stdio MCP core (spec 2026-09-29-0006; dependency
|
|
18
|
+
# contract legout/tau-mcp spec 2026-09-29-0002, accepted M2).
|
|
19
|
+
"tau-mcp>=0.1.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[dependency-groups]
|
|
23
|
+
dev = [
|
|
24
|
+
"pytest>=8.0",
|
|
25
|
+
"ruff>=0.6",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[tool.uv.sources]
|
|
29
|
+
# Local development only: resolve ariadne-data from the repository checkout
|
|
30
|
+
# without publishing it. This is uv tool configuration, never published
|
|
31
|
+
# dependency metadata (issue #23).
|
|
32
|
+
ariadne-data = { path = ".." }
|
|
33
|
+
# Same non-published pattern for the owned tau-mcp core (issue #25): the
|
|
34
|
+
# separate repository checkout, never a PyPI substitute.
|
|
35
|
+
tau-mcp = { path = "/home/volker/coding/tau-mcp" }
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["src"]
|
|
39
|
+
|
|
40
|
+
[tool.pytest.ini_options]
|
|
41
|
+
testpaths = ["tests"]
|
|
42
|
+
addopts = "-q"
|
|
43
|
+
|
|
44
|
+
[tool.pyright]
|
|
45
|
+
venvPath = "."
|
|
46
|
+
venv = ".venv"
|
|
47
|
+
extraPaths = ["..", "../src", "src"]
|
|
48
|
+
executionEnvironments = [
|
|
49
|
+
{ root = "tests", extraPaths = ["..", "../src", "src"] },
|
|
50
|
+
{ root = "src", extraPaths = ["../src", "src"] },
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.ruff]
|
|
54
|
+
line-length = 100
|
|
55
|
+
target-version = "py312"
|
|
56
|
+
|
|
57
|
+
[tool.ruff.lint]
|
|
58
|
+
select = ["E", "F", "W", "I", "UP"]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""ariadne-agent: installable conversation backend over native Ariadne tools.
|
|
2
|
+
|
|
3
|
+
A separately installable distribution (ADR-0014, spec 2026-09-29-0006): it
|
|
4
|
+
depends on ``ariadne-data`` (never the unrelated PyPI ``ariadne`` GraphQL
|
|
5
|
+
project) and ``tau-ai``, uses ``tau_agent.AgentHarness`` for the loop and
|
|
6
|
+
``tau_ai`` providers for the model, and never imports or invokes ``tau_coding``
|
|
7
|
+
at runtime, never launches a server, and never grants CLI, shell, or raw SQL
|
|
8
|
+
tools. Analyst (issue #23) and Authoring (issue #24) roles in this revision;
|
|
9
|
+
source approval and Promotion stay human-only, outside the agent.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from ariadne_agent.session import (
|
|
13
|
+
ROLES,
|
|
14
|
+
AgentSession,
|
|
15
|
+
AgentSessionEvent,
|
|
16
|
+
AssistantText,
|
|
17
|
+
RunCompleted,
|
|
18
|
+
SessionError,
|
|
19
|
+
ToolActivity,
|
|
20
|
+
open_session,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"ROLES",
|
|
25
|
+
"AgentSession",
|
|
26
|
+
"AgentSessionEvent",
|
|
27
|
+
"AssistantText",
|
|
28
|
+
"RunCompleted",
|
|
29
|
+
"SessionError",
|
|
30
|
+
"ToolActivity",
|
|
31
|
+
"open_session",
|
|
32
|
+
]
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""Analyst role tools: QueryRuntime's public operations as native tools.
|
|
2
|
+
|
|
3
|
+
Thin wrappers only (spec 2026-09-29-0006 session/role interface): compilation,
|
|
4
|
+
grain checks, read-only execution, context attachment, proposal staging and
|
|
5
|
+
terminology resolution all live in ``ariadne-data``; nothing is reimplemented
|
|
6
|
+
here. The tool set is DELIMITED to Ariadne's baseline query operations plus
|
|
7
|
+
``resolve_term`` (issue #42/K2: shared terminology resolution over the pinned
|
|
8
|
+
admitted context — read-only, no inspection/edit authority) and the two
|
|
9
|
+
Context Layer browsing operations ``list_context_pages``/``read_context_page``
|
|
10
|
+
(issue #60, spec 0015/ADR-0020: token-free listing and path-addressed reading
|
|
11
|
+
of served and staged pages — read-only, draft-labeled): the wider Query
|
|
12
|
+
surface that ariadne-data's MCP registrations expose (query_metrics/
|
|
13
|
+
query_detail, Answer Examples) does NOT silently expand this session's
|
|
14
|
+
permissions (issue #38/G6 — registrations are tool-surface separation).
|
|
15
|
+
Which runtime the session wraps — a managed Git project's pinned admission through
|
|
16
|
+
``QueryRuntime.open_project``, or an explicit legacy artifact root — is
|
|
17
|
+
resolved once at session open by :func:`ariadne_agent.session._open_runtime`.
|
|
18
|
+
Each tool's payload is the runtime's verbatim JSON envelope — values, served
|
|
19
|
+
metric page, full Policy bodies and citations, staging paths, lifecycle
|
|
20
|
+
``context_signals`` envelopes, and ``governed`` labels are never stripped or
|
|
21
|
+
summarized. Runtime failures
|
|
22
|
+
propagate with their verbatim message and are reported to the model as tool
|
|
23
|
+
errors. No CLI, no shell, no server, no raw SQL, no source-approval,
|
|
24
|
+
admission or Promotion tool.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
from collections.abc import Mapping
|
|
29
|
+
|
|
30
|
+
from ariadne.query import QueryRuntime
|
|
31
|
+
from tau_agent.messages import TextContent
|
|
32
|
+
from tau_agent.tools import AgentTool, AgentToolResult, ToolExecutor
|
|
33
|
+
|
|
34
|
+
TOOL_NAMES = (
|
|
35
|
+
"list_metrics",
|
|
36
|
+
"search_context",
|
|
37
|
+
"get_metric",
|
|
38
|
+
"list_datasets",
|
|
39
|
+
"define_metric",
|
|
40
|
+
"resolve_term", # K2 (issue #42): alongside the five baseline tools
|
|
41
|
+
"list_context_pages", # spec 0015 (issue #60): token-free page listing
|
|
42
|
+
"read_context_page", # spec 0015 (issue #60): path-addressed page reading
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _payload(value: object) -> AgentToolResult:
|
|
47
|
+
"""One verbatim JSON payload — the runtime envelope is the whole contract."""
|
|
48
|
+
return AgentToolResult(content=[TextContent(text=json.dumps(value, default=str))])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def analyst_tools(runtime: QueryRuntime) -> list[AgentTool]:
|
|
52
|
+
"""The Analyst tool set: Ariadne's five baseline query operations plus the
|
|
53
|
+
K2 read-only term resolver (issue #42) — nothing else."""
|
|
54
|
+
|
|
55
|
+
# Each wrapper mirrors the runtime's own signature and the routing language
|
|
56
|
+
# the ariadne-data MCP facade uses (spec rev 4 §8.12: fresh consuming agents
|
|
57
|
+
# discover the ad-hoc path through the facade itself).
|
|
58
|
+
async def list_metrics(
|
|
59
|
+
tool_call_id: str, arguments: Mapping, signal=None, on_update=None
|
|
60
|
+
) -> AgentToolResult:
|
|
61
|
+
return _payload(await runtime.list_metrics())
|
|
62
|
+
|
|
63
|
+
async def search_context(
|
|
64
|
+
tool_call_id: str, arguments: Mapping, signal=None, on_update=None
|
|
65
|
+
) -> AgentToolResult:
|
|
66
|
+
limit = arguments.get("limit", 10)
|
|
67
|
+
return _payload(await runtime.search_context(arguments["query"], limit))
|
|
68
|
+
|
|
69
|
+
async def get_metric(
|
|
70
|
+
tool_call_id: str, arguments: Mapping, signal=None, on_update=None
|
|
71
|
+
) -> AgentToolResult:
|
|
72
|
+
return _payload(
|
|
73
|
+
await runtime.get_metric(
|
|
74
|
+
arguments["name"], arguments.get("dimensions"), arguments.get("range")
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
async def list_datasets(
|
|
79
|
+
tool_call_id: str, arguments: Mapping, signal=None, on_update=None
|
|
80
|
+
) -> AgentToolResult:
|
|
81
|
+
return _payload(await runtime.list_datasets())
|
|
82
|
+
|
|
83
|
+
async def define_metric(
|
|
84
|
+
tool_call_id: str, arguments: Mapping, signal=None, on_update=None
|
|
85
|
+
) -> AgentToolResult:
|
|
86
|
+
return _payload(await runtime.define_metric(arguments["definition"]))
|
|
87
|
+
|
|
88
|
+
async def resolve_term(
|
|
89
|
+
tool_call_id: str, arguments: Mapping, signal=None, on_update=None
|
|
90
|
+
) -> AgentToolResult:
|
|
91
|
+
return _payload(
|
|
92
|
+
await runtime.resolve_term(arguments["request"], part=arguments.get("part", 1))
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
async def list_context_pages(
|
|
96
|
+
tool_call_id: str, arguments: Mapping, signal=None, on_update=None
|
|
97
|
+
) -> AgentToolResult:
|
|
98
|
+
return _payload(await runtime.list_context_pages())
|
|
99
|
+
|
|
100
|
+
async def read_context_page(
|
|
101
|
+
tool_call_id: str, arguments: Mapping, signal=None, on_update=None
|
|
102
|
+
) -> AgentToolResult:
|
|
103
|
+
return _payload(
|
|
104
|
+
await runtime.read_context_page(arguments["path"], draft=arguments.get("draft", False))
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
executor: dict[str, ToolExecutor] = {
|
|
108
|
+
"list_metrics": list_metrics,
|
|
109
|
+
"search_context": search_context,
|
|
110
|
+
"get_metric": get_metric,
|
|
111
|
+
"list_datasets": list_datasets,
|
|
112
|
+
"define_metric": define_metric,
|
|
113
|
+
"resolve_term": resolve_term,
|
|
114
|
+
"list_context_pages": list_context_pages,
|
|
115
|
+
"read_context_page": read_context_page,
|
|
116
|
+
}
|
|
117
|
+
descriptions = {
|
|
118
|
+
"list_metrics": (
|
|
119
|
+
"Governed metrics with descriptions and available dimensions; the list "
|
|
120
|
+
"ends with the explicit routing hint to the ad-hoc path. Start every "
|
|
121
|
+
"question here: if a governed metric answers it, query it governed."
|
|
122
|
+
),
|
|
123
|
+
"search_context": (
|
|
124
|
+
"Search the Context Layer (OKF pages) for business rules; ranked "
|
|
125
|
+
"matches, each with its full page body. Search before any novel "
|
|
126
|
+
"question — the rule you are about to rediscover is probably already "
|
|
127
|
+
"a policy page. Includes auto-staged ad-hoc drafts, marked draft: true. "
|
|
128
|
+
"Each hit carries its page's context_signals advisory view (lifecycle "
|
|
129
|
+
"status, freshness, verification tier; a context_stale/context_deprecated "
|
|
130
|
+
"warning names that page)."
|
|
131
|
+
),
|
|
132
|
+
"get_metric": (
|
|
133
|
+
"Rows plus compiled SQL for a named governed metric — never raw SQL. "
|
|
134
|
+
"The response carries a context field: the full served OKF metric "
|
|
135
|
+
"page plus the full bodies of every policy it cites. Report those "
|
|
136
|
+
"caveats and paths with every number. The context also carries a "
|
|
137
|
+
"context_signals advisory envelope whose warnings — context_stale "
|
|
138
|
+
"(reached expiry), context_deprecated (deprecated page), "
|
|
139
|
+
"context_unavailable (required metric/Policy context missing or "
|
|
140
|
+
"unreadable) — name consumed pages with lifecycle problems: when "
|
|
141
|
+
"the answer relies on warned context, add one brief caveat naming "
|
|
142
|
+
"the affected Policy/page. Absent optional expiry/verification "
|
|
143
|
+
"metadata is not a warning and needs no caveat; warnings never "
|
|
144
|
+
"change values, labels or permissions. The response also carries "
|
|
145
|
+
"a `data_signals` advisory view of SUPPLIED source-quality reports "
|
|
146
|
+
"on the metric's directly cited table pages (per-asset coverage; "
|
|
147
|
+
"per-report age/applicability/limitations, reported checks and "
|
|
148
|
+
"optional watermark; warnings such as data_report_failed/"
|
|
149
|
+
"data_report_expired): when the answer relies on an asset a "
|
|
150
|
+
"failed/expired report warning names, add ONE brief caveat naming "
|
|
151
|
+
"the report id and its as-of scope (reported outcome + report/"
|
|
152
|
+
"validity time). A supplied report is a claim reported as of its "
|
|
153
|
+
"observation/report time — never independent validation, execution "
|
|
154
|
+
"attestation or live source health; unknown applicability/partial "
|
|
155
|
+
"coverage stays unknown (a scope/version mismatch is never a "
|
|
156
|
+
"matching pass) and an event-time watermark is an event boundary, "
|
|
157
|
+
"not completeness or currency. Unrelated or absent optional "
|
|
158
|
+
"reports demand no caveat; data warnings never change values, "
|
|
159
|
+
"labels or permissions."
|
|
160
|
+
),
|
|
161
|
+
"list_datasets": (
|
|
162
|
+
"List every queryable table in the attached sources with its columns "
|
|
163
|
+
"and types. Pairs with define_metric: pick tables here, then define "
|
|
164
|
+
"an ad-hoc metric for any question no governed metric answers. "
|
|
165
|
+
"Bounded RECORD selection (the latest N raw rows of one table, no "
|
|
166
|
+
"aggregation) is a separate operation this fixed toolset does not "
|
|
167
|
+
"expose — report such questions unsupported rather than aggregating "
|
|
168
|
+
"records away; ariadne-data's CLI/MCP facade serves it as query_records."
|
|
169
|
+
),
|
|
170
|
+
"define_metric": (
|
|
171
|
+
"Define an ad-hoc metric for a question no governed metric answers: "
|
|
172
|
+
"compiled, executed read-only, auto-staged for human promotion. The "
|
|
173
|
+
"definition runs through the same compile/grain gate; success returns "
|
|
174
|
+
"rows, the compiled SQL, governed: false, and the staging paths of "
|
|
175
|
+
"the auto-staged proposal. Joins must be grain-preserving. This is an "
|
|
176
|
+
"AGGREGATION tool: a bounded record request (the latest N raw rows of "
|
|
177
|
+
"one table) is not a metric definition, and this fixed toolset has no "
|
|
178
|
+
"record operation — report record questions unsupported instead of "
|
|
179
|
+
"silently aggregating them; ariadne-data's CLI/MCP facade serves them as "
|
|
180
|
+
"query_records (governed: false, never staged)."
|
|
181
|
+
),
|
|
182
|
+
"resolve_term": (
|
|
183
|
+
"Resolve a shared business term or an exact {resource, column} field "
|
|
184
|
+
"lookup against the admitted context: bounded concept/representation "
|
|
185
|
+
"matches with their exact field bindings, required scope, ambiguity "
|
|
186
|
+
"and claim/confirmation status (a mapping is confirmed ONLY by "
|
|
187
|
+
"recorded human admission evidence bound to the exact reviewed "
|
|
188
|
+
"assertion — hypotheses stay unconfirmed leads), plus the AS-OF "
|
|
189
|
+
"evidence freshness and page/source citations to report. Whole "
|
|
190
|
+
"aliases/phrases match case-insensitively, never substrings. "
|
|
191
|
+
"Read-only: a language alias never authorizes a join or "
|
|
192
|
+
"a transform."
|
|
193
|
+
),
|
|
194
|
+
"list_context_pages": (
|
|
195
|
+
"List every Context Layer page — served and staged — without query "
|
|
196
|
+
"tokens: path, draft flag, title and each page's context_signals "
|
|
197
|
+
"advisory view, served before staged. Metadata only, never bodies. "
|
|
198
|
+
"The token-free discovery view for orientation and onboarding; "
|
|
199
|
+
"pair with read_context_page for full rule text."
|
|
200
|
+
),
|
|
201
|
+
"read_context_page": (
|
|
202
|
+
"Read one exact Context Layer page by its bundle-relative POSIX path: "
|
|
203
|
+
"full content plus draft flag and context_signals. Follow "
|
|
204
|
+
"cross-references and known paths directly; staged drafts require "
|
|
205
|
+
"draft: true and are never verified guidance. An absolute, "
|
|
206
|
+
"escaping, non-markdown or unknown path is refused naming the path."
|
|
207
|
+
),
|
|
208
|
+
}
|
|
209
|
+
schemas = {
|
|
210
|
+
"list_metrics": {"type": "object", "properties": {}, "additionalProperties": False},
|
|
211
|
+
"search_context": {
|
|
212
|
+
"type": "object",
|
|
213
|
+
"properties": {
|
|
214
|
+
"query": {"type": "string"},
|
|
215
|
+
"limit": {"type": "integer", "minimum": 1},
|
|
216
|
+
},
|
|
217
|
+
"required": ["query"],
|
|
218
|
+
"additionalProperties": False,
|
|
219
|
+
},
|
|
220
|
+
"get_metric": {
|
|
221
|
+
"type": "object",
|
|
222
|
+
"properties": {
|
|
223
|
+
"name": {"type": "string"},
|
|
224
|
+
"dimensions": {"type": "array", "items": {"type": "string"}},
|
|
225
|
+
"range": {
|
|
226
|
+
"type": "object",
|
|
227
|
+
"properties": {
|
|
228
|
+
"start": {"type": "string"},
|
|
229
|
+
"end": {"type": "string"},
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
"required": ["name"],
|
|
234
|
+
"additionalProperties": False,
|
|
235
|
+
},
|
|
236
|
+
"list_datasets": {"type": "object", "properties": {}, "additionalProperties": False},
|
|
237
|
+
"define_metric": {
|
|
238
|
+
"type": "object",
|
|
239
|
+
"properties": {
|
|
240
|
+
"definition": {
|
|
241
|
+
"type": "object",
|
|
242
|
+
"properties": {
|
|
243
|
+
"name": {"type": "string"},
|
|
244
|
+
"description": {"type": "string"},
|
|
245
|
+
"source": {"type": "string"},
|
|
246
|
+
"expression": {"type": "string"},
|
|
247
|
+
"dimensions": {"type": "array", "items": {"type": "string"}},
|
|
248
|
+
"time_field": {"type": "string"},
|
|
249
|
+
},
|
|
250
|
+
"required": ["name", "description", "source", "expression"],
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
"required": ["definition"],
|
|
254
|
+
"additionalProperties": False,
|
|
255
|
+
},
|
|
256
|
+
"resolve_term": {
|
|
257
|
+
"type": "object",
|
|
258
|
+
"properties": {
|
|
259
|
+
"request": {
|
|
260
|
+
"anyOf": [
|
|
261
|
+
{"type": "string", "minLength": 1},
|
|
262
|
+
{
|
|
263
|
+
"type": "object",
|
|
264
|
+
"properties": {
|
|
265
|
+
"resource": {"type": "string", "minLength": 1},
|
|
266
|
+
"column": {"type": "string", "minLength": 1},
|
|
267
|
+
},
|
|
268
|
+
"required": ["resource", "column"],
|
|
269
|
+
"additionalProperties": False,
|
|
270
|
+
},
|
|
271
|
+
]
|
|
272
|
+
},
|
|
273
|
+
"part": {"type": "integer", "minimum": 1},
|
|
274
|
+
},
|
|
275
|
+
"required": ["request"],
|
|
276
|
+
"additionalProperties": False,
|
|
277
|
+
},
|
|
278
|
+
"list_context_pages": {
|
|
279
|
+
"type": "object",
|
|
280
|
+
"properties": {},
|
|
281
|
+
"additionalProperties": False,
|
|
282
|
+
},
|
|
283
|
+
"read_context_page": {
|
|
284
|
+
"type": "object",
|
|
285
|
+
"properties": {
|
|
286
|
+
"path": {"type": "string", "minLength": 1},
|
|
287
|
+
"draft": {"type": "boolean"},
|
|
288
|
+
},
|
|
289
|
+
"required": ["path"],
|
|
290
|
+
"additionalProperties": False,
|
|
291
|
+
},
|
|
292
|
+
}
|
|
293
|
+
return [
|
|
294
|
+
AgentTool(
|
|
295
|
+
name=name,
|
|
296
|
+
label=name.replace("_", " ").title(),
|
|
297
|
+
description=descriptions[name],
|
|
298
|
+
parameters=schemas[name],
|
|
299
|
+
execute_fn=executor[name],
|
|
300
|
+
)
|
|
301
|
+
for name in TOOL_NAMES
|
|
302
|
+
]
|