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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ ]