agentfluent 0.1.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.
Files changed (38) hide show
  1. agentfluent-0.1.0/LICENSE +21 -0
  2. agentfluent-0.1.0/PKG-INFO +52 -0
  3. agentfluent-0.1.0/README.md +22 -0
  4. agentfluent-0.1.0/pyproject.toml +78 -0
  5. agentfluent-0.1.0/src/agentfluent/__init__.py +8 -0
  6. agentfluent-0.1.0/src/agentfluent/agents/__init__.py +0 -0
  7. agentfluent-0.1.0/src/agentfluent/agents/extractor.py +78 -0
  8. agentfluent-0.1.0/src/agentfluent/agents/models.py +71 -0
  9. agentfluent-0.1.0/src/agentfluent/analytics/__init__.py +0 -0
  10. agentfluent-0.1.0/src/agentfluent/analytics/agent_metrics.py +127 -0
  11. agentfluent-0.1.0/src/agentfluent/analytics/pipeline.py +278 -0
  12. agentfluent-0.1.0/src/agentfluent/analytics/pricing.py +96 -0
  13. agentfluent-0.1.0/src/agentfluent/analytics/tokens.py +137 -0
  14. agentfluent-0.1.0/src/agentfluent/analytics/tools.py +93 -0
  15. agentfluent-0.1.0/src/agentfluent/cli/__init__.py +0 -0
  16. agentfluent-0.1.0/src/agentfluent/cli/commands/__init__.py +0 -0
  17. agentfluent-0.1.0/src/agentfluent/cli/commands/analyze.py +162 -0
  18. agentfluent-0.1.0/src/agentfluent/cli/commands/config_check.py +110 -0
  19. agentfluent-0.1.0/src/agentfluent/cli/commands/list_cmd.py +165 -0
  20. agentfluent-0.1.0/src/agentfluent/cli/exit_codes.py +18 -0
  21. agentfluent-0.1.0/src/agentfluent/cli/formatters/__init__.py +0 -0
  22. agentfluent-0.1.0/src/agentfluent/cli/formatters/helpers.py +59 -0
  23. agentfluent-0.1.0/src/agentfluent/cli/formatters/json_output.py +60 -0
  24. agentfluent-0.1.0/src/agentfluent/cli/formatters/table.py +358 -0
  25. agentfluent-0.1.0/src/agentfluent/cli/main.py +63 -0
  26. agentfluent-0.1.0/src/agentfluent/config/__init__.py +31 -0
  27. agentfluent-0.1.0/src/agentfluent/config/models.py +118 -0
  28. agentfluent-0.1.0/src/agentfluent/config/scanner.py +146 -0
  29. agentfluent-0.1.0/src/agentfluent/config/scoring.py +299 -0
  30. agentfluent-0.1.0/src/agentfluent/core/__init__.py +0 -0
  31. agentfluent-0.1.0/src/agentfluent/core/discovery.py +158 -0
  32. agentfluent-0.1.0/src/agentfluent/core/parser.py +251 -0
  33. agentfluent-0.1.0/src/agentfluent/core/session.py +133 -0
  34. agentfluent-0.1.0/src/agentfluent/diagnostics/__init__.py +51 -0
  35. agentfluent-0.1.0/src/agentfluent/diagnostics/correlator.py +248 -0
  36. agentfluent-0.1.0/src/agentfluent/diagnostics/models.py +75 -0
  37. agentfluent-0.1.0/src/agentfluent/diagnostics/signals.py +162 -0
  38. agentfluent-0.1.0/src/agentfluent/py.typed +0 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fred Pearce
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentfluent
3
+ Version: 0.1.0
4
+ Summary: Local-first agent analytics with prompt diagnostics
5
+ Keywords: claude,agent,analytics,cli,llm,diagnostics
6
+ Author: Fred Pearce
7
+ Author-email: Fred Pearce <fpearce@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Typing :: Typed
20
+ Requires-Dist: typer>=0.15
21
+ Requires-Dist: rich>=14.0
22
+ Requires-Dist: pydantic>=2.0
23
+ Requires-Dist: pyyaml>=6.0
24
+ Requires-Python: >=3.12
25
+ Project-URL: Homepage, https://github.com/frederick-douglas-pearce/agentfluent
26
+ Project-URL: Repository, https://github.com/frederick-douglas-pearce/agentfluent
27
+ Project-URL: Issues, https://github.com/frederick-douglas-pearce/agentfluent/issues
28
+ Project-URL: Changelog, https://github.com/frederick-douglas-pearce/agentfluent/blob/main/CHANGELOG.md
29
+ Description-Content-Type: text/markdown
30
+
31
+ # AgentFluent
32
+
33
+ Local-first agent analytics with prompt diagnostics. The tools that exist tell you what your agent did. This tool tells you what to change.
34
+
35
+ ## What Is This?
36
+
37
+ AgentFluent analyzes AI agent session data (Claude Code subagents + Agent SDK) to provide:
38
+
39
+ - **Agent execution analytics** -- token usage, cost tracking, tool call patterns, error rates
40
+ - **Agent prompt diagnostics** -- score system prompts against best practices, correlate agent behavior to prompt quality issues, generate specific improvement recommendations
41
+ - **Prompt regression detection** -- compare agent behavior across prompt versions
42
+ - **Agent configuration assessment** -- tool access audit, model selection analysis, hook coverage
43
+
44
+ ## Background
45
+
46
+ Born from [CodeFluent](https://github.com/frederick-douglas-pearce/codefluent) research identifying a gap in the agent observability market: existing tools monitor agent execution (traces, latency, cost) but none evaluate agent *quality* or diagnose *prompt-level issues* from local session data.
47
+
48
+ See `docs/AGENT_ANALYTICS_RESEARCH.md` for the full market analysis and technical feasibility study.
49
+
50
+ ## Status
51
+
52
+ Early stage -- defining scope, architecture, and initial backlog.
@@ -0,0 +1,22 @@
1
+ # AgentFluent
2
+
3
+ Local-first agent analytics with prompt diagnostics. The tools that exist tell you what your agent did. This tool tells you what to change.
4
+
5
+ ## What Is This?
6
+
7
+ AgentFluent analyzes AI agent session data (Claude Code subagents + Agent SDK) to provide:
8
+
9
+ - **Agent execution analytics** -- token usage, cost tracking, tool call patterns, error rates
10
+ - **Agent prompt diagnostics** -- score system prompts against best practices, correlate agent behavior to prompt quality issues, generate specific improvement recommendations
11
+ - **Prompt regression detection** -- compare agent behavior across prompt versions
12
+ - **Agent configuration assessment** -- tool access audit, model selection analysis, hook coverage
13
+
14
+ ## Background
15
+
16
+ Born from [CodeFluent](https://github.com/frederick-douglas-pearce/codefluent) research identifying a gap in the agent observability market: existing tools monitor agent execution (traces, latency, cost) but none evaluate agent *quality* or diagnose *prompt-level issues* from local session data.
17
+
18
+ See `docs/AGENT_ANALYTICS_RESEARCH.md` for the full market analysis and technical feasibility study.
19
+
20
+ ## Status
21
+
22
+ Early stage -- defining scope, architecture, and initial backlog.
@@ -0,0 +1,78 @@
1
+ [project]
2
+ name = "agentfluent"
3
+ version = "0.1.0" # x-release-please-version
4
+ description = "Local-first agent analytics with prompt diagnostics"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [
9
+ { name = "Fred Pearce", email = "fpearce@gmail.com" }
10
+ ]
11
+ requires-python = ">=3.12"
12
+ keywords = ["claude", "agent", "analytics", "cli", "llm", "diagnostics"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Environment :: Console",
16
+ "Intended Audience :: Developers",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Topic :: Software Development :: Libraries :: Python Modules",
22
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
23
+ "Typing :: Typed",
24
+ ]
25
+ dependencies = [
26
+ "typer>=0.15",
27
+ "rich>=14.0",
28
+ "pydantic>=2.0",
29
+ "pyyaml>=6.0",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/frederick-douglas-pearce/agentfluent"
34
+ Repository = "https://github.com/frederick-douglas-pearce/agentfluent"
35
+ Issues = "https://github.com/frederick-douglas-pearce/agentfluent/issues"
36
+ Changelog = "https://github.com/frederick-douglas-pearce/agentfluent/blob/main/CHANGELOG.md"
37
+
38
+ [project.scripts]
39
+ agentfluent = "agentfluent.cli.main:app"
40
+
41
+ [dependency-groups]
42
+ dev = [
43
+ "pytest>=8.0",
44
+ "pytest-cov>=6.0",
45
+ "ruff>=0.11",
46
+ "mypy>=1.15",
47
+ "types-pyyaml>=6.0",
48
+ ]
49
+
50
+ [build-system]
51
+ requires = ["uv_build>=0.9.5,<0.12.0"]
52
+ build-backend = "uv_build"
53
+
54
+ [tool.pytest.ini_options]
55
+ testpaths = ["tests"]
56
+ markers = [
57
+ "integration: tests that require real session data (deselect with '-m \"not integration\"')",
58
+ ]
59
+
60
+ [tool.coverage.run]
61
+ source = ["agentfluent"]
62
+
63
+ [tool.coverage.report]
64
+ show_missing = true
65
+ skip_empty = true
66
+
67
+ [tool.ruff]
68
+ target-version = "py312"
69
+ line-length = 100
70
+
71
+ [tool.ruff.lint]
72
+ select = ["E", "F", "I", "N", "W", "UP"]
73
+
74
+ [tool.mypy]
75
+ python_version = "3.12"
76
+ strict = true
77
+ warn_return_any = true
78
+ warn_unused_configs = true
@@ -0,0 +1,8 @@
1
+ """AgentFluent: Local-first agent analytics with prompt diagnostics."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("agentfluent")
7
+ except PackageNotFoundError:
8
+ __version__ = "0.0.0"
File without changes
@@ -0,0 +1,78 @@
1
+ """Extract agent invocations from parsed session messages.
2
+
3
+ Identifies Agent tool_use blocks in assistant messages and matches them
4
+ to their corresponding tool_result blocks to build AgentInvocation objects.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from agentfluent.agents.models import AgentInvocation, is_builtin_agent
10
+ from agentfluent.core.session import SessionMessage
11
+
12
+
13
+ def extract_agent_invocations(messages: list[SessionMessage]) -> list[AgentInvocation]:
14
+ """Extract agent invocations from a list of parsed session messages.
15
+
16
+ Scans for assistant messages containing Agent tool_use blocks (name == "Agent"),
17
+ then matches each to its corresponding tool_result by tool_use_id.
18
+
19
+ Args:
20
+ messages: Parsed session messages from the JSONL parser.
21
+
22
+ Returns:
23
+ List of AgentInvocation objects in session order.
24
+ """
25
+ # Build a lookup of tool_result messages by tool_use_id
26
+ tool_results: dict[str, SessionMessage] = {}
27
+ for msg in messages:
28
+ if msg.type == "tool_result" and msg.tool_use_id:
29
+ tool_results[msg.tool_use_id] = msg
30
+
31
+ invocations: list[AgentInvocation] = []
32
+
33
+ for msg in messages:
34
+ if msg.type != "assistant":
35
+ continue
36
+
37
+ for tool_use in msg.tool_use_blocks:
38
+ if tool_use.name != "Agent":
39
+ continue
40
+
41
+ agent_type = tool_use.input.get("subagent_type", "unknown")
42
+ description = tool_use.input.get("description", "")
43
+ prompt = tool_use.input.get("prompt", "")
44
+
45
+ # Match to tool_result
46
+ result = tool_results.get(tool_use.id)
47
+
48
+ total_tokens = None
49
+ tool_uses_count = None
50
+ duration_ms = None
51
+ agent_id = None
52
+ output_text = ""
53
+
54
+ if result is not None:
55
+ output_text = result.text
56
+
57
+ if result.metadata is not None:
58
+ total_tokens = result.metadata.total_tokens
59
+ tool_uses_count = result.metadata.tool_uses
60
+ duration_ms = result.metadata.duration_ms
61
+ agent_id = result.metadata.agent_id
62
+
63
+ invocations.append(
64
+ AgentInvocation(
65
+ agent_type=agent_type,
66
+ is_builtin=is_builtin_agent(agent_type),
67
+ description=description,
68
+ prompt=prompt,
69
+ tool_use_id=tool_use.id,
70
+ total_tokens=total_tokens,
71
+ tool_uses=tool_uses_count,
72
+ duration_ms=duration_ms,
73
+ agent_id=agent_id,
74
+ output_text=output_text,
75
+ )
76
+ )
77
+
78
+ return invocations
@@ -0,0 +1,71 @@
1
+ """Data models for agent invocations extracted from session data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ # Built-in agent types (case-insensitive matching).
8
+ # Update this set as Anthropic adds new built-in agents.
9
+ BUILTIN_AGENT_TYPES: frozenset[str] = frozenset(
10
+ {
11
+ "explore",
12
+ "plan",
13
+ "general-purpose",
14
+ "code-reviewer",
15
+ "statusline-setup",
16
+ "claude-code-guide",
17
+ }
18
+ )
19
+
20
+
21
+ def is_builtin_agent(agent_type: str) -> bool:
22
+ """Check if an agent type is a built-in Claude Code agent."""
23
+ return agent_type.lower() in BUILTIN_AGENT_TYPES
24
+
25
+
26
+ @dataclass
27
+ class AgentInvocation:
28
+ """A single agent invocation extracted from a session.
29
+
30
+ Combines data from the Agent tool_use block (in the assistant message)
31
+ with the corresponding tool_result (including metadata).
32
+ """
33
+
34
+ agent_type: str
35
+ """Agent type (e.g., 'pm', 'Explore', 'Plan')."""
36
+
37
+ is_builtin: bool
38
+ """Whether this is a built-in Claude Code agent."""
39
+
40
+ description: str
41
+ """The description passed to the Agent tool."""
42
+
43
+ prompt: str
44
+ """The delegation prompt sent to the agent."""
45
+
46
+ tool_use_id: str
47
+ """The tool_use ID linking this invocation to its tool_result."""
48
+
49
+ # From tool_result metadata (may be None if no metadata or agent was interrupted)
50
+ total_tokens: int | None = None
51
+ tool_uses: int | None = None
52
+ duration_ms: int | None = None
53
+ agent_id: str | None = None
54
+
55
+ # From tool_result content
56
+ output_text: str = ""
57
+ """The agent's final summary/output text."""
58
+
59
+ @property
60
+ def tokens_per_tool_use(self) -> float | None:
61
+ """Average tokens per tool call. None if data unavailable."""
62
+ if self.total_tokens is not None and self.tool_uses and self.tool_uses > 0:
63
+ return self.total_tokens / self.tool_uses
64
+ return None
65
+
66
+ @property
67
+ def duration_per_tool_use(self) -> float | None:
68
+ """Average duration (ms) per tool call. None if data unavailable."""
69
+ if self.duration_ms is not None and self.tool_uses and self.tool_uses > 0:
70
+ return self.duration_ms / self.tool_uses
71
+ return None
@@ -0,0 +1,127 @@
1
+ """Per-agent execution metrics.
2
+
3
+ Computes invocation counts, token usage, and efficiency metrics
4
+ grouped by agent type. Reports token counts and percentages rather
5
+ than dollar costs, since tool_result metadata only provides aggregate
6
+ total_tokens without the per-category breakdown needed for accurate
7
+ cost estimation.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass, field
13
+
14
+ from agentfluent.agents.models import AgentInvocation
15
+
16
+
17
+ @dataclass
18
+ class AgentTypeMetrics:
19
+ """Execution metrics for a single agent type."""
20
+
21
+ agent_type: str
22
+ is_builtin: bool
23
+ invocation_count: int = 0
24
+ total_tokens: int = 0
25
+ total_tool_uses: int = 0
26
+ total_duration_ms: int = 0
27
+ avg_tokens_per_tool_use: float | None = None
28
+ avg_duration_per_tool_use: float | None = None
29
+
30
+ @property
31
+ def avg_tokens_per_invocation(self) -> float | None:
32
+ if self.invocation_count > 0 and self.total_tokens > 0:
33
+ return self.total_tokens / self.invocation_count
34
+ return None
35
+
36
+ @property
37
+ def avg_duration_per_invocation(self) -> float | None:
38
+ if self.invocation_count > 0 and self.total_duration_ms > 0:
39
+ return self.total_duration_ms / self.invocation_count
40
+ return None
41
+
42
+
43
+ @dataclass
44
+ class AgentMetrics:
45
+ """Aggregated agent execution metrics for a session."""
46
+
47
+ by_agent_type: dict[str, AgentTypeMetrics] = field(default_factory=dict)
48
+ """Per-agent-type metrics, keyed by agent_type."""
49
+
50
+ total_invocations: int = 0
51
+ total_agent_tokens: int = 0
52
+ total_agent_duration_ms: int = 0
53
+
54
+ builtin_invocations: int = 0
55
+ custom_invocations: int = 0
56
+
57
+ agent_token_percentage: float = 0.0
58
+ """Agent tokens as percentage of session total tokens (0-100).
59
+ Set by the caller who has session-level token data."""
60
+
61
+
62
+ def compute_agent_metrics(
63
+ invocations: list[AgentInvocation],
64
+ session_total_tokens: int = 0,
65
+ ) -> AgentMetrics:
66
+ """Compute per-agent-type execution metrics from extracted invocations.
67
+
68
+ Groups invocations by agent_type and computes counts, totals, and averages.
69
+ Invocations with missing metadata are counted but excluded from averages.
70
+
71
+ Args:
72
+ invocations: Extracted agent invocations from a session.
73
+ session_total_tokens: Total session tokens for computing agent percentage.
74
+
75
+ Returns:
76
+ AgentMetrics with per-type breakdowns and summary totals.
77
+ """
78
+ by_type: dict[str, AgentTypeMetrics] = {}
79
+
80
+ for inv in invocations:
81
+ key = inv.agent_type.lower()
82
+ metrics = by_type.get(key)
83
+ if metrics is None:
84
+ metrics = AgentTypeMetrics(agent_type=inv.agent_type, is_builtin=inv.is_builtin)
85
+ by_type[key] = metrics
86
+
87
+ metrics.invocation_count += 1
88
+
89
+ if inv.total_tokens is not None:
90
+ metrics.total_tokens += inv.total_tokens
91
+ if inv.tool_uses is not None:
92
+ metrics.total_tool_uses += inv.tool_uses
93
+ if inv.duration_ms is not None:
94
+ metrics.total_duration_ms += inv.duration_ms
95
+
96
+ # Compute averages
97
+ for metrics in by_type.values():
98
+ if metrics.total_tool_uses > 0:
99
+ if metrics.total_tokens > 0:
100
+ metrics.avg_tokens_per_tool_use = metrics.total_tokens / metrics.total_tool_uses
101
+ if metrics.total_duration_ms > 0:
102
+ metrics.avg_duration_per_tool_use = (
103
+ metrics.total_duration_ms / metrics.total_tool_uses
104
+ )
105
+
106
+ # Aggregate totals
107
+ total_invocations = sum(m.invocation_count for m in by_type.values())
108
+ total_agent_tokens = sum(m.total_tokens for m in by_type.values())
109
+ total_agent_duration = sum(m.total_duration_ms for m in by_type.values())
110
+ builtin_count = sum(m.invocation_count for m in by_type.values() if m.is_builtin)
111
+ custom_count = sum(m.invocation_count for m in by_type.values() if not m.is_builtin)
112
+
113
+ agent_token_pct = (
114
+ round(total_agent_tokens / session_total_tokens * 100, 1)
115
+ if session_total_tokens > 0 and total_agent_tokens > 0
116
+ else 0.0
117
+ )
118
+
119
+ return AgentMetrics(
120
+ by_agent_type=by_type,
121
+ total_invocations=total_invocations,
122
+ total_agent_tokens=total_agent_tokens,
123
+ total_agent_duration_ms=total_agent_duration,
124
+ builtin_invocations=builtin_count,
125
+ custom_invocations=custom_count,
126
+ agent_token_percentage=agent_token_pct,
127
+ )