Skip to content

Commit 24af202

Browse files
EightRiceclaude
andcommitted
fix: open-source cleanup, capability tiers, provider analysis
- Secrets moved from hardcoded strings to env vars (OAuth credentials) - .gitignore overhauled for open-source readiness - .env.example updated with new env var placeholders - LICENSE file added - Model capability tiers (4-level) in provider_manager and snapshot - Claude provider analysis documented in anthropic.py Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0a1d478 commit 24af202

9 files changed

Lines changed: 209 additions & 32 deletions

File tree

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,10 @@ HEARTBEAT_INTERVAL=60
2525

2626
# Testnet
2727
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
28+
29+
# Google OAuth2 (for Calendar/Tasks connector)
30+
GOOGLE_CLIENT_ID=your_google_client_id_here
31+
GOOGLE_CLIENT_SECRET=your_google_client_secret_here
32+
33+
# Voice module (optional, for voice connector)
34+
VOICE_MODULE_DIR=./voice

.gitignore

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,46 @@
1-
FUNDING_LEADS.md
2-
CLAUDE.md
3-
rpb_gemini.txt
4-
rpb_speaker_notes.md
5-
share.md
6-
artefacts/
1+
# Environment and secrets
72
.env
8-
node_modules/
3+
*.env
4+
5+
# Python
96
__pycache__/
107
*.pyc
11-
.claude-workers/
12-
artifacts/
13-
training_run*.log
14-
deployment-addresses.json
15-
# ATN runtime
8+
*.pyo
169
*.egg-info/
1710
dist/
1811
build/
1912
.pytest_cache/
13+
14+
# Node
15+
node_modules/
16+
bun.lockb
17+
bun.lock
18+
19+
# IDE
20+
.vscode/
21+
.idea/
22+
23+
# Logs
24+
*.log
25+
training_run*.log
26+
27+
# Data and artifacts
28+
data/
29+
artifacts/
30+
artefacts/
2031
test_data_*/
32+
33+
# ATN runtime
2134
*.running.json
2235
latest.txt
23-
bun.lockb
24-
bun.lock
36+
deployment-addresses.json
37+
.claude-workers/
38+
39+
# Project-specific
40+
FUNDING_LEADS.md
41+
CLAUDE.md
42+
rpb_gemini.txt
43+
rpb_speaker_notes.md
44+
share.md
2545
/agents/
26-
*.log
2746
PLAN.md

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 ATN Contributors
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.

atn/connectors/google_calendar/server.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,8 @@
4242
GCAL_BASE = "https://www.googleapis.com/calendar/v3"
4343
GTASKS_BASE = "https://tasks.googleapis.com/tasks/v1"
4444

45-
CLIENT_ID = (
46-
"901620659302-e7pkd1o0bsooedaothhkln9rhc5ah8k5"
47-
".apps.googleusercontent.com"
48-
)
49-
CLIENT_SECRET = "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"
45+
CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID", "")
46+
CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET", "")
5047
TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"
5148

5249
# Mutable token state — refreshed automatically on 401.

atn/connectors/voice/server.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""MCP server for voice / text-to-speech -- lets agents speak aloud.
22
3-
Wraps the voice module (c:/code/voice) as MCP tools so that any
3+
Wraps an external voice module as MCP tools so that any
44
ATN agent can produce audible speech on the user's machine.
55
66
Supports two backends:
@@ -11,7 +11,7 @@
1111
1212
Usage (standalone):
1313
python server.py
14-
python server.py --voice-dir C:/code/voice
14+
python server.py --voice-dir /path/to/voice
1515
1616
Usage (via ATN):
1717
Launched automatically by ConnectorManager.start("voice")
@@ -22,6 +22,7 @@
2222
import argparse
2323
import json
2424
import logging
25+
import os
2526
import sys
2627
from typing import Optional
2728

@@ -38,7 +39,10 @@
3839

3940
# -- Configuration -------------------------------------------------------------
4041

41-
VOICE_MODULE_DIR = r"C:\code\voice"
42+
VOICE_MODULE_DIR = os.environ.get(
43+
"VOICE_MODULE_DIR",
44+
os.path.join(os.path.dirname(__file__), "..", "..", "..", "voice"),
45+
)
4246

4347
# -- MCP Server ----------------------------------------------------------------
4448

atn/oauth.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import asyncio
1717
import json
1818
import logging
19+
import os
1920
import urllib.parse
2021
import urllib.request
2122
from typing import Any
@@ -24,11 +25,11 @@
2425

2526
# -- Google OAuth2 constants --------------------------------------------------
2627

27-
GOOGLE_CLIENT_ID = (
28-
"901620659302-e7pkd1o0bsooedaothhkln9rhc5ah8k5"
29-
".apps.googleusercontent.com"
28+
GOOGLE_CLIENT_ID = os.environ.get(
29+
"GOOGLE_CLIENT_ID",
30+
"",
3031
)
31-
GOOGLE_CLIENT_SECRET = "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"
32+
GOOGLE_CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET", "")
3233
GOOGLE_AUTH_ENDPOINT = "https://accounts.google.com/o/oauth2/auth"
3334
GOOGLE_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"
3435

atn/providers/anthropic.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,46 @@
55
- Tool definition translation
66
- Response parsing (text blocks, tool_use blocks)
77
- Retry with exponential backoff for transient errors (429, 503, 529)
8+
9+
Provider Unification Analysis: AnthropicProvider vs BridgeProvider
10+
===================================================================
11+
Two paths exist for running Claude models:
12+
13+
1. BridgeProvider (bridge.py) — Claude Agent SDK via TypeScript subprocess
14+
- Spawns a bun subprocess running bridge/claude-bridge.ts
15+
- Uses Claude Agent SDK's native multi-turn orchestration (send_orchestrate)
16+
- The SDK handles its own agentic loop, context compaction, session resumption
17+
- Built-in shell tools (Bash, Read, Write, Glob, Grep) live in the SDK process
18+
- ATN framework tools are relayed as MCP tool_call/tool_result over stdin/stdout
19+
- Streaming events arrive on stderr as @@EVENT@@ NDJSON lines
20+
- Supports session_id for cross-turn prompt caching and context persistence
21+
- Requires bun + node_modules installed; heavier subprocess lifecycle
22+
23+
2. AnthropicProvider (this file) — Python REST client via httpx
24+
- Direct HTTP calls to the Anthropic Messages API
25+
- Multi-turn orchestration uses the generic loop in base.py (send_orchestrate)
26+
which calls send_stream() repeatedly, executing tool calls in Python
27+
- Shell tools come from atn/shell_tools.py (Python implementations)
28+
- No subprocess, no SDK dependency — pure Python
29+
- No native session resumption or context compaction
30+
- Lighter weight, easier to test, works without bun/node
31+
32+
Key differences:
33+
- BridgeProvider has native context compaction (SDK auto-summarises on overflow)
34+
- BridgeProvider has session resumption via session_id (persistent conversations)
35+
- BridgeProvider emits richer events (tool_use_start/result, compaction, thinking)
36+
- AnthropicProvider uses the base class generic agentic loop (simpler but less capable)
37+
- AnthropicProvider could be enhanced to use the Anthropic Python SDK's agent loop
38+
for parity with BridgeProvider without the TypeScript subprocess overhead
39+
40+
Path forward:
41+
- Short-term: Both paths coexist. BridgeProvider is used for the orchestrator
42+
(needs session persistence + compaction). AnthropicProvider is used for
43+
delegates and one-shot calls (lighter weight, no subprocess overhead).
44+
- Medium-term: Consider migrating AnthropicProvider to use the Anthropic Python
45+
SDK's agent loop (anthropic.Agent) instead of the generic base.py loop.
46+
This would give parity with BridgeProvider (compaction, session management)
47+
without the TypeScript subprocess, and could eventually replace BridgeProvider.
848
"""
949
from __future__ import annotations
1050

atn/runtime/provider_manager.py

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,71 @@
2323

2424
log = logging.getLogger(__name__)
2525

26+
# ---------------------------------------------------------------------------
27+
# Model capability tiers
28+
# ---------------------------------------------------------------------------
29+
# Tier 4: orchestrator — multi-agent coordination, complex planning, 20+ tools
30+
# Tier 3: autonomous — independent multi-step execution, 10+ tools, self-correction
31+
# Tier 2: tool-use — reliable tool calling, structured instructions, single-task
32+
# Tier 1: conversational — text in/out, unreliable with tools
33+
34+
TIER_LABELS: dict[int, str] = {
35+
4: "orchestrator",
36+
3: "autonomous",
37+
2: "tool-use",
38+
1: "conversational",
39+
}
40+
41+
# Known model → tier mappings. Prefix matching is used: the longest prefix
42+
# that matches wins. Unknown models default to tier 2.
43+
_MODEL_TIERS: dict[str, int] = {
44+
# Anthropic
45+
"claude-opus-4": 4,
46+
"claude-sonnet-4": 3,
47+
"claude-haiku-4": 2,
48+
"claude-sonnet-3.5": 3,
49+
"claude-haiku-3.5": 2,
50+
# OpenAI
51+
"o3": 4,
52+
"o1": 3,
53+
"gpt-4o": 3,
54+
"gpt-4-turbo": 3,
55+
"gpt-4": 3,
56+
"gpt-3.5": 2,
57+
# Google
58+
"gemini-2.5-pro": 4,
59+
"gemini-2.5-flash": 3,
60+
"gemini-2.0-flash": 3,
61+
"gemini-2.0-pro": 4,
62+
"gemini-1.5-pro": 3,
63+
"gemini-1.5-flash": 2,
64+
}
65+
66+
_DEFAULT_TIER = 2
67+
68+
69+
def get_model_tier(model_id: str) -> int:
70+
"""Return the capability tier (1-4) for a model ID.
71+
72+
Uses longest-prefix matching against _MODEL_TIERS.
73+
Unknown models default to tier 2 (tool-use).
74+
"""
75+
if not model_id:
76+
return _DEFAULT_TIER
77+
lower = model_id.lower()
78+
best_match = 0
79+
best_tier = _DEFAULT_TIER
80+
for prefix, tier in _MODEL_TIERS.items():
81+
if lower.startswith(prefix.lower()) and len(prefix) > best_match:
82+
best_match = len(prefix)
83+
best_tier = tier
84+
return best_tier
85+
86+
87+
def get_tier_label(tier: int) -> str:
88+
"""Return the human-readable label for a tier number."""
89+
return TIER_LABELS.get(tier, "unknown")
90+
2691

2792
class ProviderManager:
2893
"""Provider configuration, resolution, probing, and lifecycle."""
@@ -341,7 +406,13 @@ def get_available_models(self, provider_name: str, *, require_active: bool = Fal
341406
{"id": "claude-haiku-4-5", "name": "Claude Haiku 4.5"},
342407
],
343408
}
344-
return _PROVIDER_MODELS.get(provider_name, [])
409+
models = _PROVIDER_MODELS.get(provider_name, [])
410+
# Enrich each model entry with capability tier
411+
for m in models:
412+
tier = get_model_tier(m["id"])
413+
m["capability_tier"] = tier
414+
m["tier_label"] = get_tier_label(tier)
415+
return models
345416

346417
# ------------------------------------------------------------------
347418
# Provider management (config UI)
@@ -374,15 +445,23 @@ async def provider_list(self) -> list[dict[str, Any]]:
374445
else:
375446
setup_hint = None
376447

448+
models = self.get_available_models(pid)
449+
# Derive orchestrator_capable from model tiers (backward compat)
450+
max_tier = max((m.get("capability_tier", _DEFAULT_TIER) for m in models), default=_DEFAULT_TIER) if models else _DEFAULT_TIER
451+
# Fall back to the static flag for providers with no model list
452+
orch_capable = max_tier >= 4 if models else info.get("orchestrator_capable", False)
453+
377454
entry: dict[str, Any] = {
378455
"id": pid,
379456
"name": info["name"],
380457
"description": info["description"],
381458
"auth_type": info["auth_type"],
382459
"configured": configured,
383460
"active": is_active,
384-
"orchestrator_capable": info.get("orchestrator_capable", False),
385-
"models": self.get_available_models(pid),
461+
"orchestrator_capable": orch_capable,
462+
"max_capability_tier": max_tier,
463+
"max_tier_label": get_tier_label(max_tier),
464+
"models": models,
386465
"setup_hint": setup_hint,
387466
}
388467

@@ -406,17 +485,22 @@ async def provider_list(self) -> list[dict[str, Any]]:
406485
pconfig = self._config.providers.get(pid)
407486
base_url = pconfig.base_url if pconfig else ""
408487

488+
default_model = pconfig.default_model if pconfig else ""
489+
custom_tier = get_model_tier(default_model)
490+
409491
entry = {
410492
"id": pid,
411493
"name": pid,
412494
"description": f"Custom OpenAI-compatible provider ({base_url})",
413495
"auth_type": "api_key",
414496
"configured": True,
415497
"active": is_active,
416-
"orchestrator_capable": True,
498+
"orchestrator_capable": custom_tier >= 4,
499+
"max_capability_tier": custom_tier,
500+
"max_tier_label": get_tier_label(custom_tier),
417501
"custom": True,
418502
"base_url": base_url,
419-
"default_model": pconfig.default_model if pconfig else "",
503+
"default_model": default_model,
420504
"models": [],
421505
"setup_hint": None,
422506
}

atn/runtime/snapshot.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import Any, TYPE_CHECKING
77

88
from ..models import AgentStatus, StepType, TaskStatus
9+
from .provider_manager import get_model_tier, get_tier_label
910

1011
if TYPE_CHECKING:
1112
from .agent_registry import AgentRegistry
@@ -124,9 +125,12 @@ def snapshot(self) -> dict:
124125
primary_provider = raw_provider
125126
fallback_providers = []
126127
orch_model = orch_defn.cognitive_model or ""
128+
orch_tier = get_model_tier(orch_model)
127129
orch_info = {
128130
"provider": primary_provider,
129131
"model": orch_model,
132+
"capability_tier": orch_tier,
133+
"tier_label": get_tier_label(orch_tier),
130134
"available_models": self.provider_manager.get_available_models(primary_provider),
131135
"fallback_providers": fallback_providers,
132136
}

0 commit comments

Comments
 (0)