hyperagents 1.2.3rc5.post3__py3-none-any.whl
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.
- agents/__init__.py +31 -0
- agents/background_monitor.py +453 -0
- agents/learning.py +460 -0
- agents/planner.py +421 -0
- agents/transcriber.py +341 -0
- civilization/__init__.py +51 -0
- civilization/api/__init__.py +7 -0
- civilization/api/civilization_api.py +361 -0
- civilization/graph/__init__.py +19 -0
- civilization/graph/knowledge_linker.py +348 -0
- civilization/graph/org_graph.py +348 -0
- civilization/graph/process_graph.py +386 -0
- civilization/ingestion/__init__.py +20 -0
- civilization/ingestion/chunker.py +273 -0
- civilization/ingestion/document_ingestor.py +358 -0
- civilization/ingestion/embedder.py +262 -0
- civilization/ingestion/extractor.py +224 -0
- civilization/interview/__init__.py +19 -0
- civilization/interview/gap_detector.py +380 -0
- civilization/interview/interviewer.py +323 -0
- civilization/interview/questioner.py +363 -0
- civilization/retrieval/__init__.py +17 -0
- civilization/retrieval/civilization_rag.py +316 -0
- civilization/retrieval/context_injector.py +342 -0
- civilization/retrieval/relevance_ranker.py +287 -0
- civilization/schema.py +299 -0
- civilization/store.py +177 -0
- civilization/sync/__init__.py +16 -0
- civilization/sync/confluence_sync.py +317 -0
- civilization/sync/gdrive_sync.py +262 -0
- civilization/sync/notion_sync.py +237 -0
- civilization/versioning/__init__.py +19 -0
- civilization/versioning/diff_engine.py +351 -0
- civilization/versioning/staleness_detector.py +303 -0
- civilization/versioning/version_manager.py +293 -0
- cli/__init__.py +1 -0
- cli/doctor.py +334 -0
- cli/hyperclaw.py +1357 -0
- cli/onboarding.py +469 -0
- core/__init__.py +1 -0
- core/connectors/__init__.py +119 -0
- core/connectors/infrastructure.py +772 -0
- core/connectors/messaging.py +586 -0
- core/connectors/productivity.py +764 -0
- core/connectors/smarthome.py +528 -0
- core/connectors/social.py +730 -0
- core/hyperrouter/__init__.py +23 -0
- core/hyperrouter/bandit.py +218 -0
- core/hyperrouter/fast_loop.py +68 -0
- core/hyperrouter/slow_loop.py +143 -0
- core/hyperstate/__init__.py +24 -0
- core/hyperstate/certifier.py +154 -0
- core/hyperstate/schema.py +160 -0
- core/hyperstate/state_manager.py +103 -0
- core/hyperstate/store.py +163 -0
- core/providers/__init__.py +121 -0
- core/providers/embeddings.py +389 -0
- core/providers/image.py +534 -0
- core/providers/llm.py +1935 -0
- core/providers/search.py +608 -0
- core/providers/voice.py +474 -0
- hyperagents-1.2.3rc5.post3.dist-info/METADATA +469 -0
- hyperagents-1.2.3rc5.post3.dist-info/RECORD +361 -0
- hyperagents-1.2.3rc5.post3.dist-info/WHEEL +5 -0
- hyperagents-1.2.3rc5.post3.dist-info/entry_points.txt +6 -0
- hyperagents-1.2.3rc5.post3.dist-info/licenses/LICENSE +21 -0
- hyperagents-1.2.3rc5.post3.dist-info/top_level.txt +13 -0
- hyperclaw/__init__.py +67 -0
- hyperclaw/__main__.py +220 -0
- hyperclaw/agent.py +775 -0
- hyperclaw/agent_coordinator.py +577 -0
- hyperclaw/anthropic_params.py +225 -0
- hyperclaw/api_utils.py +85 -0
- hyperclaw/approval_telegram.py +209 -0
- hyperclaw/approval_terminal.py +80 -0
- hyperclaw/attachments.py +177 -0
- hyperclaw/branding.py +55 -0
- hyperclaw/brief/__init__.py +11 -0
- hyperclaw/brief/cli.py +204 -0
- hyperclaw/brief/collect.py +146 -0
- hyperclaw/brief/compose.py +333 -0
- hyperclaw/brief/diff.py +110 -0
- hyperclaw/brief/errors.py +99 -0
- hyperclaw/brief/google.py +269 -0
- hyperclaw/brief/google_auth.py +294 -0
- hyperclaw/brief/links.py +124 -0
- hyperclaw/brief/service.py +424 -0
- hyperclaw/brief/store.py +205 -0
- hyperclaw/brief/tasks.py +200 -0
- hyperclaw/chat_scope.py +115 -0
- hyperclaw/cli_tui.py +497 -0
- hyperclaw/client_guard.py +717 -0
- hyperclaw/connection_check.py +113 -0
- hyperclaw/cost_guard.py +1728 -0
- hyperclaw/default_config/agents.yaml +534 -0
- hyperclaw/default_config/hyperclaw.yaml +27 -0
- hyperclaw/default_config/models.yaml +161 -0
- hyperclaw/deployment.py +39 -0
- hyperclaw/digest/__init__.py +9 -0
- hyperclaw/digest/cli.py +117 -0
- hyperclaw/digest/errors.py +100 -0
- hyperclaw/digest/gitlab.py +187 -0
- hyperclaw/digest/http.py +113 -0
- hyperclaw/digest/redact.py +32 -0
- hyperclaw/digest/service.py +271 -0
- hyperclaw/digest/slack.py +223 -0
- hyperclaw/digest/state.py +124 -0
- hyperclaw/digest/summarize.py +198 -0
- hyperclaw/digest/tokens.py +143 -0
- hyperclaw/distribution.py +46 -0
- hyperclaw/documents.py +351 -0
- hyperclaw/feeds.py +339 -0
- hyperclaw/google_workspace/__init__.py +5 -0
- hyperclaw/google_workspace/api.py +151 -0
- hyperclaw/google_workspace/auth.py +397 -0
- hyperclaw/google_workspace/calendar.py +157 -0
- hyperclaw/google_workspace/cli.py +72 -0
- hyperclaw/google_workspace/drive.py +236 -0
- hyperclaw/google_workspace/errors.py +58 -0
- hyperclaw/google_workspace/gmail.py +295 -0
- hyperclaw/google_workspace/meet.py +122 -0
- hyperclaw/google_workspace/tools.py +319 -0
- hyperclaw/hyperspeed.py +57 -0
- hyperclaw/integrations_layer.py +1427 -0
- hyperclaw/live_state.py +188 -0
- hyperclaw/managed_config.py +245 -0
- hyperclaw/media_hub.py +179 -0
- hyperclaw/memory_manager.py +584 -0
- hyperclaw/model_catalog.py +551 -0
- hyperclaw/model_policy.py +211 -0
- hyperclaw/model_router.py +595 -0
- hyperclaw/model_selector.py +72 -0
- hyperclaw/naming.py +284 -0
- hyperclaw/notices.py +65 -0
- hyperclaw/oai_stream.py +106 -0
- hyperclaw/orchestrator.py +591 -0
- hyperclaw/outbox.py +82 -0
- hyperclaw/persona.py +367 -0
- hyperclaw/prometheus.py +219 -0
- hyperclaw/prompt_assembly.py +157 -0
- hyperclaw/provider_setup.py +146 -0
- hyperclaw/providers.py +340 -0
- hyperclaw/qualification.py +341 -0
- hyperclaw/qualification_keys/hyper-d292fd127cef5a3e.pub +1 -0
- hyperclaw/qualification_records/anthropic--claude-sonnet-5-5.json +14 -0
- hyperclaw/reasoning_display.py +35 -0
- hyperclaw/scheduler.py +454 -0
- hyperclaw/server.py +847 -0
- hyperclaw/setup.py +627 -0
- hyperclaw/skills.py +717 -0
- hyperclaw/solomon.py +442 -0
- hyperclaw/swarm.py +533 -0
- hyperclaw/taint.py +832 -0
- hyperclaw/team/__init__.py +28 -0
- hyperclaw/team/dispatcher.py +431 -0
- hyperclaw/team/genesis.py +106 -0
- hyperclaw/team/lead.py +110 -0
- hyperclaw/team/model.py +135 -0
- hyperclaw/team/morning.py +238 -0
- hyperclaw/team/repos.py +66 -0
- hyperclaw/team/roster.py +264 -0
- hyperclaw/team/specialist.py +235 -0
- hyperclaw/team/status.py +163 -0
- hyperclaw/team/watchers.py +112 -0
- hyperclaw/teamsetup/__init__.py +17 -0
- hyperclaw/teamsetup/checks.py +208 -0
- hyperclaw/teamsetup/item.py +391 -0
- hyperclaw/teamsetup/oauth_loopback.py +84 -0
- hyperclaw/teamsetup/onboard.py +550 -0
- hyperclaw/teamsetup/profile.py +82 -0
- hyperclaw/teamsetup/secrets.py +313 -0
- hyperclaw/teamsetup/selfcheck.py +295 -0
- hyperclaw/teamsetup/service.py +495 -0
- hyperclaw/teamsetup/slack_oauth.py +442 -0
- hyperclaw/teamsetup/tty.py +62 -0
- hyperclaw/telegram_bot.py +1106 -0
- hyperclaw/telegram_intake.py +126 -0
- hyperclaw/telegram_pairing.py +360 -0
- hyperclaw/terminal_provider.py +323 -0
- hyperclaw/tools/__init__.py +1 -0
- hyperclaw/tools/approval.py +949 -0
- hyperclaw/tools/args.py +212 -0
- hyperclaw/tools/catalog.py +244 -0
- hyperclaw/tools/classify.py +266 -0
- hyperclaw/tools/effects.py +724 -0
- hyperclaw/tools/egress.py +210 -0
- hyperclaw/tools/envelope.py +188 -0
- hyperclaw/tools/guard.py +367 -0
- hyperclaw/tools/ledger.py +339 -0
- hyperclaw/tools/receipts.py +70 -0
- hyperclaw/tools/registry.py +318 -0
- hyperclaw/tools/service_guard.py +151 -0
- hyperclaw/tools/surfaces.py +54 -0
- hyperclaw/tools/workspace.py +87 -0
- hyperclaw/trading/__init__.py +77 -0
- hyperclaw/truth_guard.py +791 -0
- hyperclaw/tts.py +124 -0
- hyperclaw/tui.py +4255 -0
- hyperclaw/tui_bridge.py +938 -0
- hyperclaw/unattended.py +60 -0
- hyperclaw/updates/__init__.py +54 -0
- hyperclaw/updates/check.py +131 -0
- hyperclaw/updates/index.py +178 -0
- hyperclaw/updates/install.py +110 -0
- hyperclaw/updates/managed.py +161 -0
- hyperclaw/updates/manifest.py +138 -0
- hyperclaw/updates/notices.py +86 -0
- hyperclaw/updates/paths.py +53 -0
- hyperclaw/updates/release.py +146 -0
- hyperclaw/updates/upgrade.py +253 -0
- hyperclaw/updates/versioning.py +104 -0
- hyperclaw/userconfig.py +119 -0
- hyperclaw/xai.py +83 -0
- integrations/__init__.py +31 -0
- integrations/automation/__init__.py +1 -0
- integrations/automation/make.py +87 -0
- integrations/automation/zapier.py +87 -0
- integrations/base.py +476 -0
- integrations/communication/__init__.py +1 -0
- integrations/communication/twilio.py +279 -0
- integrations/communication/zoom.py +290 -0
- integrations/data/__init__.py +1 -0
- integrations/data/google_analytics.py +135 -0
- integrations/data/postgres.py +182 -0
- integrations/data/supabase_connector.py +202 -0
- integrations/data/weather_feeds.py +450 -0
- integrations/developer/__init__.py +9 -0
- integrations/developer/github.py +406 -0
- integrations/developer/gitlab.py +298 -0
- integrations/enterprise/__init__.py +13 -0
- integrations/enterprise/confluence.py +266 -0
- integrations/enterprise/hubspot.py +293 -0
- integrations/enterprise/jira.py +314 -0
- integrations/enterprise/salesforce.py +286 -0
- integrations/finance/__init__.py +1 -0
- integrations/finance/polymarket_client.py +247 -0
- integrations/finance/quickbooks.py +281 -0
- integrations/finance/stripe.py +261 -0
- integrations/gateway.py +238 -0
- integrations/messaging/__init__.py +32 -0
- integrations/messaging/discord.py +159 -0
- integrations/messaging/email.py +401 -0
- integrations/messaging/imessage.py +443 -0
- integrations/messaging/signal.py +175 -0
- integrations/messaging/slack.py +217 -0
- integrations/messaging/sms.py +175 -0
- integrations/messaging/teams.py +208 -0
- integrations/messaging/telegram.py +260 -0
- integrations/messaging/whatsapp.py +203 -0
- integrations/productivity/__init__.py +17 -0
- integrations/productivity/airtable.py +217 -0
- integrations/productivity/asana.py +248 -0
- integrations/productivity/google/__init__.py +19 -0
- integrations/productivity/google/base.py +89 -0
- integrations/productivity/google/calendar.py +272 -0
- integrations/productivity/google/docs.py +202 -0
- integrations/productivity/google/drive.py +268 -0
- integrations/productivity/google/gmail.py +279 -0
- integrations/productivity/google/meet.py +218 -0
- integrations/productivity/google/sheets.py +222 -0
- integrations/productivity/google/tasks.py +199 -0
- integrations/productivity/linear.py +279 -0
- integrations/productivity/microsoft/__init__.py +13 -0
- integrations/productivity/microsoft/base.py +93 -0
- integrations/productivity/microsoft/calendar.py +207 -0
- integrations/productivity/microsoft/onedrive.py +222 -0
- integrations/productivity/microsoft/outlook.py +241 -0
- integrations/productivity/microsoft/sharepoint.py +211 -0
- integrations/productivity/notion.py +243 -0
- integrations/productivity/todoist.py +250 -0
- integrations/productivity/trello.py +244 -0
- integrations/storage/__init__.py +1 -0
- integrations/storage/box.py +279 -0
- integrations/storage/dropbox.py +215 -0
- integrations/telegram_inbound.py +179 -0
- integrations/telegram_poller.py +151 -0
- memory/__init__.py +1 -0
- memory/causal_graph.py +125 -0
- memory/impact_tracker.py +88 -0
- memory/vector_memory.py +436 -0
- models/__init__.py +12 -0
- models/chatjimmy_client.py +210 -0
- models/claude_client.py +235 -0
- models/claude_code_subagent.py +192 -0
- models/openai_client.py +120 -0
- models/router.py +162 -0
- recursive/discovery_loop.py +118 -0
- recursive/optimization_loop.py +135 -0
- recursive/validation_loop.py +38 -0
- security/__init__.py +1 -0
- security/audit_logger.py +106 -0
- security/filesystem_guard.py +100 -0
- security/hypershield.py +99 -0
- security/network_guard.py +97 -0
- security/policy_engine.py +198 -0
- swarm/__init__.py +1 -0
- swarm/agents/__init__.py +0 -0
- swarm/agents/base.py +230 -0
- swarm/agents/business/__init__.py +0 -0
- swarm/agents/business/counsel.py +31 -0
- swarm/agents/business/herald.py +32 -0
- swarm/agents/business/ledger.py +26 -0
- swarm/agents/business/nexus.py +30 -0
- swarm/agents/business/ops.py +30 -0
- swarm/agents/business/pipeline.py +28 -0
- swarm/agents/business/revenue.py +30 -0
- swarm/agents/business/sovereign.py +31 -0
- swarm/agents/business/strategos.py +27 -0
- swarm/agents/business/talent.py +27 -0
- swarm/agents/business/venture.py +32 -0
- swarm/agents/comms/__init__.py +1 -0
- swarm/agents/comms/cipher.py +32 -0
- swarm/agents/comms/echo_comms.py +31 -0
- swarm/agents/comms/envoy.py +31 -0
- swarm/agents/comms/herald_scheduler.py +33 -0
- swarm/agents/comms/pulse.py +29 -0
- swarm/agents/creative/__init__.py +0 -0
- swarm/agents/creative/author.py +27 -0
- swarm/agents/creative/lens.py +25 -0
- swarm/agents/executive/__init__.py +1 -0
- swarm/agents/intelligence/__init__.py +1 -0
- swarm/agents/intelligence/arbiter.py +30 -0
- swarm/agents/intelligence/sentinel.py +30 -0
- swarm/agents/media/__init__.py +0 -0
- swarm/agents/personal/__init__.py +0 -0
- swarm/agents/personal/atlas.py +27 -0
- swarm/agents/personal/hearth.py +26 -0
- swarm/agents/personal/midas.py +27 -0
- swarm/agents/personal/navigator.py +27 -0
- swarm/agents/personal/nourish.py +31 -0
- swarm/agents/personal/vitals.py +33 -0
- swarm/agents/recursive/__init__.py +0 -0
- swarm/agents/recursive/alchemist.py +73 -0
- swarm/agents/recursive/calibrator.py +45 -0
- swarm/agents/recursive/scout.py +97 -0
- swarm/agents/scientific/__init__.py +0 -0
- swarm/agents/scientific/cosmos.py +36 -0
- swarm/agents/scientific/gaia.py +26 -0
- swarm/agents/scientific/medicus.py +33 -0
- swarm/agents/scientific/oracle_agent.py +39 -0
- swarm/agents/scientific/scribe.py +39 -0
- swarm/agents/talent/__init__.py +0 -0
- swarm/agents/talent/deal.py +28 -0
- swarm/agents/talent/roster.py +29 -0
- swarm/agents/talent/scout.py +29 -0
- swarm/agents/talent/stage.py +27 -0
- swarm/agents/tech/__init__.py +0 -0
- swarm/agents/tech/aegis.py +28 -0
- swarm/agents/tech/bridge.py +28 -0
- swarm/agents/tech/forge.py +28 -0
- swarm/agents/trading/__init__.py +1 -0
- swarm/agents/trading/global_prediction_engine.py +611 -0
- swarm/agents/trading/polymarket_trader.py +165 -0
- swarm/agents/trading/prediction_strategist.py +218 -0
- swarm/autogen_bridge.py +68 -0
- swarm/bid_protocol.py +196 -0
- swarm/dispatcher.py +527 -0
- swarm/nexus.py +181 -0
- swarm/registry.py +112 -0
- telegram_listener.py +403 -0
- ui/tui/app.py +214 -0
agents/learning.py
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Assistant Self-Improvement System — Learn from successes and failures.
|
|
4
|
+
Tracks patterns, updates instincts, reflects on performance.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import json
|
|
10
|
+
import sqlite3
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from datetime import datetime, timedelta
|
|
13
|
+
from typing import Dict, List, Optional, Any
|
|
14
|
+
from collections import defaultdict
|
|
15
|
+
|
|
16
|
+
from hyperclaw.taint import training_sink
|
|
17
|
+
|
|
18
|
+
# Use user's ~/.hyperclaw directory, not package location
|
|
19
|
+
HYPERCLAW_ROOT = Path(os.environ.get("HYPERCLAW_ROOT", Path.home() / ".hyperclaw"))
|
|
20
|
+
LEARNING_DB = HYPERCLAW_ROOT / "memory" / "learning.db"
|
|
21
|
+
INSTINCTS_FILE = HYPERCLAW_ROOT / "memory" / "instincts.md"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def init_db():
|
|
25
|
+
"""Initialize the learning database."""
|
|
26
|
+
LEARNING_DB.parent.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
conn = sqlite3.connect(str(LEARNING_DB))
|
|
28
|
+
conn.executescript("""
|
|
29
|
+
CREATE TABLE IF NOT EXISTS interactions (
|
|
30
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
31
|
+
timestamp TEXT NOT NULL,
|
|
32
|
+
action_type TEXT NOT NULL,
|
|
33
|
+
action_detail TEXT,
|
|
34
|
+
outcome TEXT NOT NULL,
|
|
35
|
+
success INTEGER NOT NULL,
|
|
36
|
+
feedback TEXT,
|
|
37
|
+
context TEXT,
|
|
38
|
+
learned TEXT
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE TABLE IF NOT EXISTS patterns (
|
|
42
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
43
|
+
pattern_type TEXT NOT NULL,
|
|
44
|
+
description TEXT NOT NULL,
|
|
45
|
+
frequency INTEGER DEFAULT 1,
|
|
46
|
+
success_rate REAL DEFAULT 0.0,
|
|
47
|
+
last_seen TEXT,
|
|
48
|
+
action TEXT
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
CREATE TABLE IF NOT EXISTS reflections (
|
|
52
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
53
|
+
timestamp TEXT NOT NULL,
|
|
54
|
+
period TEXT NOT NULL,
|
|
55
|
+
summary TEXT NOT NULL,
|
|
56
|
+
insights TEXT,
|
|
57
|
+
improvements TEXT
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_interactions_type ON interactions(action_type);
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_interactions_outcome ON interactions(outcome);
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_patterns_type ON patterns(pattern_type);
|
|
63
|
+
""")
|
|
64
|
+
conn.commit()
|
|
65
|
+
conn.close()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Never let a storage problem kill the import — callers can retry lazily.
|
|
69
|
+
try:
|
|
70
|
+
init_db()
|
|
71
|
+
except sqlite3.OperationalError as _e:
|
|
72
|
+
import logging
|
|
73
|
+
logging.getLogger(__name__).warning("learning db unavailable: %s", _e)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@training_sink("learning.log_interaction")
|
|
77
|
+
def log_interaction(
|
|
78
|
+
action_type: str,
|
|
79
|
+
action_detail: str,
|
|
80
|
+
outcome: str,
|
|
81
|
+
success: bool,
|
|
82
|
+
feedback: str = None,
|
|
83
|
+
context: Dict = None
|
|
84
|
+
) -> str:
|
|
85
|
+
"""Log an interaction for learning."""
|
|
86
|
+
conn = sqlite3.connect(str(LEARNING_DB))
|
|
87
|
+
|
|
88
|
+
conn.execute("""
|
|
89
|
+
INSERT INTO interactions (timestamp, action_type, action_detail, outcome, success, feedback, context)
|
|
90
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
91
|
+
""", (
|
|
92
|
+
datetime.now().isoformat(),
|
|
93
|
+
action_type,
|
|
94
|
+
action_detail,
|
|
95
|
+
outcome,
|
|
96
|
+
1 if success else 0,
|
|
97
|
+
feedback,
|
|
98
|
+
json.dumps(context) if context else None
|
|
99
|
+
))
|
|
100
|
+
|
|
101
|
+
conn.commit()
|
|
102
|
+
conn.close()
|
|
103
|
+
|
|
104
|
+
# Update patterns
|
|
105
|
+
_update_patterns(action_type, success)
|
|
106
|
+
|
|
107
|
+
# If failure, try to learn from it
|
|
108
|
+
if not success and feedback:
|
|
109
|
+
return _learn_from_failure(action_type, action_detail, outcome, feedback)
|
|
110
|
+
|
|
111
|
+
return f"Logged: {action_type} - {'success' if success else 'failure'}"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@training_sink("learning.update_patterns")
|
|
115
|
+
def _update_patterns(action_type: str, success: bool):
|
|
116
|
+
"""Update pattern statistics."""
|
|
117
|
+
conn = sqlite3.connect(str(LEARNING_DB))
|
|
118
|
+
|
|
119
|
+
# Check if pattern exists
|
|
120
|
+
existing = conn.execute(
|
|
121
|
+
"SELECT id, frequency, success_rate FROM patterns WHERE pattern_type = ?",
|
|
122
|
+
(action_type,)
|
|
123
|
+
).fetchone()
|
|
124
|
+
|
|
125
|
+
if existing:
|
|
126
|
+
pattern_id, freq, rate = existing
|
|
127
|
+
new_freq = freq + 1
|
|
128
|
+
# Running average
|
|
129
|
+
new_rate = ((rate * freq) + (1 if success else 0)) / new_freq
|
|
130
|
+
conn.execute("""
|
|
131
|
+
UPDATE patterns SET frequency = ?, success_rate = ?, last_seen = ?
|
|
132
|
+
WHERE id = ?
|
|
133
|
+
""", (new_freq, new_rate, datetime.now().isoformat(), pattern_id))
|
|
134
|
+
else:
|
|
135
|
+
conn.execute("""
|
|
136
|
+
INSERT INTO patterns (pattern_type, description, frequency, success_rate, last_seen)
|
|
137
|
+
VALUES (?, ?, 1, ?, ?)
|
|
138
|
+
""", (action_type, f"Pattern for {action_type}", 1.0 if success else 0.0, datetime.now().isoformat()))
|
|
139
|
+
|
|
140
|
+
conn.commit()
|
|
141
|
+
conn.close()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@training_sink("learning.learn_from_failure")
|
|
145
|
+
def _learn_from_failure(action_type: str, detail: str, outcome: str, feedback: str) -> str:
|
|
146
|
+
"""Extract learning from a failure."""
|
|
147
|
+
# Simple learning extraction
|
|
148
|
+
learning = f"When {action_type}: {detail[:50]}... resulted in {outcome}. User feedback: {feedback}"
|
|
149
|
+
|
|
150
|
+
# Update the interaction with the learning
|
|
151
|
+
conn = sqlite3.connect(str(LEARNING_DB))
|
|
152
|
+
conn.execute("""
|
|
153
|
+
UPDATE interactions SET learned = ?
|
|
154
|
+
WHERE id = (SELECT id FROM interactions WHERE action_type = ? AND action_detail = ?
|
|
155
|
+
ORDER BY timestamp DESC LIMIT 1)
|
|
156
|
+
""", (learning, action_type, detail))
|
|
157
|
+
conn.commit()
|
|
158
|
+
conn.close()
|
|
159
|
+
|
|
160
|
+
# Check if this should become an instinct
|
|
161
|
+
_maybe_create_instinct(action_type, feedback)
|
|
162
|
+
|
|
163
|
+
return f"Learned: {learning[:100]}..."
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _maybe_create_instinct(action_type: str, feedback: str):
|
|
167
|
+
"""Check if a pattern should become an instinct."""
|
|
168
|
+
conn = sqlite3.connect(str(LEARNING_DB))
|
|
169
|
+
|
|
170
|
+
# Count recent failures of this type
|
|
171
|
+
recent_failures = conn.execute("""
|
|
172
|
+
SELECT COUNT(*) FROM interactions
|
|
173
|
+
WHERE action_type = ? AND success = 0
|
|
174
|
+
AND timestamp > datetime('now', '-7 days')
|
|
175
|
+
""", (action_type,)).fetchone()[0]
|
|
176
|
+
|
|
177
|
+
conn.close()
|
|
178
|
+
|
|
179
|
+
# If 3+ failures in a week, create an instinct
|
|
180
|
+
if recent_failures >= 3:
|
|
181
|
+
_add_instinct(f"CAUTION: {action_type}", feedback)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@training_sink("learning.add_instinct")
|
|
185
|
+
def _add_instinct(title: str, description: str):
|
|
186
|
+
"""Add a new instinct to instincts.md."""
|
|
187
|
+
if not INSTINCTS_FILE.exists():
|
|
188
|
+
INSTINCTS_FILE.write_text("# Assistant Instincts\n\nLearned behaviors and reflexes.\n\n")
|
|
189
|
+
|
|
190
|
+
content = INSTINCTS_FILE.read_text()
|
|
191
|
+
|
|
192
|
+
# Check if already exists
|
|
193
|
+
if title.lower() in content.lower():
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
# Add new instinct
|
|
197
|
+
new_instinct = f"\n## {title}\n\n{description}\n\n*Learned: {datetime.now().strftime('%Y-%m-%d')}*\n"
|
|
198
|
+
INSTINCTS_FILE.write_text(content + new_instinct)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def get_stats(days: int = 7) -> Dict:
|
|
202
|
+
"""Get learning statistics."""
|
|
203
|
+
conn = sqlite3.connect(str(LEARNING_DB))
|
|
204
|
+
|
|
205
|
+
cutoff = (datetime.now() - timedelta(days=days)).isoformat()
|
|
206
|
+
|
|
207
|
+
total = conn.execute(
|
|
208
|
+
"SELECT COUNT(*) FROM interactions WHERE timestamp > ?", (cutoff,)
|
|
209
|
+
).fetchone()[0]
|
|
210
|
+
|
|
211
|
+
successes = conn.execute(
|
|
212
|
+
"SELECT COUNT(*) FROM interactions WHERE timestamp > ? AND success = 1", (cutoff,)
|
|
213
|
+
).fetchone()[0]
|
|
214
|
+
|
|
215
|
+
by_type = conn.execute("""
|
|
216
|
+
SELECT action_type, COUNT(*), SUM(success)
|
|
217
|
+
FROM interactions WHERE timestamp > ?
|
|
218
|
+
GROUP BY action_type
|
|
219
|
+
ORDER BY COUNT(*) DESC LIMIT 10
|
|
220
|
+
""", (cutoff,)).fetchall()
|
|
221
|
+
|
|
222
|
+
recent_failures = conn.execute("""
|
|
223
|
+
SELECT action_type, action_detail, feedback
|
|
224
|
+
FROM interactions WHERE timestamp > ? AND success = 0
|
|
225
|
+
ORDER BY timestamp DESC LIMIT 5
|
|
226
|
+
""", (cutoff,)).fetchall()
|
|
227
|
+
|
|
228
|
+
conn.close()
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
"period_days": days,
|
|
232
|
+
"total_interactions": total,
|
|
233
|
+
"successes": successes,
|
|
234
|
+
"success_rate": round(successes / total * 100, 1) if total > 0 else 0,
|
|
235
|
+
"by_type": [
|
|
236
|
+
{"type": t, "count": c, "successes": s, "rate": round(s/c*100, 1) if c > 0 else 0}
|
|
237
|
+
for t, c, s in by_type
|
|
238
|
+
],
|
|
239
|
+
"recent_failures": [
|
|
240
|
+
{"type": t, "detail": d[:50], "feedback": f}
|
|
241
|
+
for t, d, f in recent_failures
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def get_patterns() -> List[Dict]:
|
|
247
|
+
"""Get learned patterns."""
|
|
248
|
+
conn = sqlite3.connect(str(LEARNING_DB))
|
|
249
|
+
|
|
250
|
+
patterns = conn.execute("""
|
|
251
|
+
SELECT pattern_type, description, frequency, success_rate, last_seen, action
|
|
252
|
+
FROM patterns ORDER BY frequency DESC LIMIT 20
|
|
253
|
+
""").fetchall()
|
|
254
|
+
|
|
255
|
+
conn.close()
|
|
256
|
+
|
|
257
|
+
return [
|
|
258
|
+
{
|
|
259
|
+
"type": p[0],
|
|
260
|
+
"description": p[1],
|
|
261
|
+
"frequency": p[2],
|
|
262
|
+
"success_rate": round(p[3] * 100, 1),
|
|
263
|
+
"last_seen": p[4],
|
|
264
|
+
"action": p[5]
|
|
265
|
+
}
|
|
266
|
+
for p in patterns
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@training_sink("learning.reflect")
|
|
271
|
+
def reflect(period: str = "day") -> Dict:
|
|
272
|
+
"""Generate a reflection on recent performance."""
|
|
273
|
+
if period == "day":
|
|
274
|
+
days = 1
|
|
275
|
+
elif period == "week":
|
|
276
|
+
days = 7
|
|
277
|
+
elif period == "month":
|
|
278
|
+
days = 30
|
|
279
|
+
else:
|
|
280
|
+
days = 7
|
|
281
|
+
|
|
282
|
+
stats = get_stats(days)
|
|
283
|
+
|
|
284
|
+
# Generate insights
|
|
285
|
+
insights = []
|
|
286
|
+
|
|
287
|
+
if stats["success_rate"] < 80:
|
|
288
|
+
insights.append(f"Success rate ({stats['success_rate']}%) is below target (80%)")
|
|
289
|
+
|
|
290
|
+
# Find problematic areas
|
|
291
|
+
for item in stats["by_type"]:
|
|
292
|
+
if item["rate"] < 70 and item["count"] >= 3:
|
|
293
|
+
insights.append(f"{item['type']}: Only {item['rate']}% success rate over {item['count']} attempts")
|
|
294
|
+
|
|
295
|
+
# Suggest improvements
|
|
296
|
+
improvements = []
|
|
297
|
+
for failure in stats["recent_failures"]:
|
|
298
|
+
if failure["feedback"]:
|
|
299
|
+
improvements.append(f"For {failure['type']}: {failure['feedback']}")
|
|
300
|
+
|
|
301
|
+
reflection = {
|
|
302
|
+
"period": period,
|
|
303
|
+
"stats": stats,
|
|
304
|
+
"insights": insights,
|
|
305
|
+
"improvements": improvements,
|
|
306
|
+
"generated_at": datetime.now().isoformat()
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
# Save reflection
|
|
310
|
+
conn = sqlite3.connect(str(LEARNING_DB))
|
|
311
|
+
conn.execute("""
|
|
312
|
+
INSERT INTO reflections (timestamp, period, summary, insights, improvements)
|
|
313
|
+
VALUES (?, ?, ?, ?, ?)
|
|
314
|
+
""", (
|
|
315
|
+
datetime.now().isoformat(),
|
|
316
|
+
period,
|
|
317
|
+
json.dumps(stats),
|
|
318
|
+
json.dumps(insights),
|
|
319
|
+
json.dumps(improvements)
|
|
320
|
+
))
|
|
321
|
+
conn.commit()
|
|
322
|
+
conn.close()
|
|
323
|
+
|
|
324
|
+
return reflection
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def get_advice(action_type: str) -> str:
|
|
328
|
+
"""Get advice based on past learnings for an action type."""
|
|
329
|
+
conn = sqlite3.connect(str(LEARNING_DB))
|
|
330
|
+
|
|
331
|
+
# Get pattern stats
|
|
332
|
+
pattern = conn.execute(
|
|
333
|
+
"SELECT success_rate, frequency FROM patterns WHERE pattern_type = ?",
|
|
334
|
+
(action_type,)
|
|
335
|
+
).fetchone()
|
|
336
|
+
|
|
337
|
+
# Get recent learnings
|
|
338
|
+
learnings = conn.execute("""
|
|
339
|
+
SELECT learned FROM interactions
|
|
340
|
+
WHERE action_type = ? AND learned IS NOT NULL
|
|
341
|
+
ORDER BY timestamp DESC LIMIT 3
|
|
342
|
+
""", (action_type,)).fetchall()
|
|
343
|
+
|
|
344
|
+
conn.close()
|
|
345
|
+
|
|
346
|
+
advice_parts = []
|
|
347
|
+
|
|
348
|
+
if pattern:
|
|
349
|
+
rate, freq = pattern
|
|
350
|
+
if rate < 0.7:
|
|
351
|
+
advice_parts.append(f"Caution: {action_type} has {rate*100:.0f}% success rate over {freq} attempts.")
|
|
352
|
+
elif rate > 0.9:
|
|
353
|
+
advice_parts.append(f"Good track record: {action_type} has {rate*100:.0f}% success rate.")
|
|
354
|
+
|
|
355
|
+
if learnings:
|
|
356
|
+
advice_parts.append("Past learnings:")
|
|
357
|
+
for (learning,) in learnings:
|
|
358
|
+
advice_parts.append(f" - {learning}")
|
|
359
|
+
|
|
360
|
+
return "\n".join(advice_parts) if advice_parts else f"No prior data for {action_type}"
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def log_success(action_type: str, detail: str, context: Dict = None) -> str:
|
|
364
|
+
"""Shorthand for logging a success."""
|
|
365
|
+
return log_interaction(action_type, detail, "completed successfully", True, context=context)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def log_failure(action_type: str, detail: str, error: str, feedback: str = None) -> str:
|
|
369
|
+
"""Shorthand for logging a failure."""
|
|
370
|
+
return log_interaction(action_type, detail, error, False, feedback=feedback)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
@training_sink("learning.log_feedback")
|
|
374
|
+
def log_feedback(feedback: str, action_type: str = None) -> str:
|
|
375
|
+
"""Log user feedback on recent actions."""
|
|
376
|
+
conn = sqlite3.connect(str(LEARNING_DB))
|
|
377
|
+
|
|
378
|
+
# Update most recent interaction
|
|
379
|
+
if action_type:
|
|
380
|
+
conn.execute("""
|
|
381
|
+
UPDATE interactions SET feedback = ?
|
|
382
|
+
WHERE id = (SELECT id FROM interactions WHERE action_type = ?
|
|
383
|
+
ORDER BY timestamp DESC LIMIT 1)
|
|
384
|
+
""", (feedback, action_type))
|
|
385
|
+
else:
|
|
386
|
+
conn.execute("""
|
|
387
|
+
UPDATE interactions SET feedback = ?
|
|
388
|
+
WHERE id = (SELECT id FROM interactions ORDER BY timestamp DESC LIMIT 1)
|
|
389
|
+
""", (feedback,))
|
|
390
|
+
|
|
391
|
+
conn.commit()
|
|
392
|
+
conn.close()
|
|
393
|
+
|
|
394
|
+
return f"Feedback recorded: {feedback[:50]}..."
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
# CLI functions for TUI integration
|
|
398
|
+
def learning_log(action: str, detail: str, success: bool, feedback: str = None) -> str:
|
|
399
|
+
"""Log an action for learning."""
|
|
400
|
+
outcome = "success" if success else "failure"
|
|
401
|
+
return log_interaction(action, detail, outcome, success, feedback=feedback)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def learning_stats(days: int = 7) -> Dict:
|
|
405
|
+
"""Get learning statistics."""
|
|
406
|
+
return get_stats(days)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def learning_patterns() -> List[Dict]:
|
|
410
|
+
"""Get learned patterns."""
|
|
411
|
+
return get_patterns()
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def learning_reflect(period: str = "week") -> Dict:
|
|
415
|
+
"""Generate a reflection."""
|
|
416
|
+
return reflect(period)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def learning_advice(action_type: str) -> str:
|
|
420
|
+
"""Get advice for an action type."""
|
|
421
|
+
return get_advice(action_type)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def learning_feedback(feedback: str) -> str:
|
|
425
|
+
"""Record feedback on recent action."""
|
|
426
|
+
return log_feedback(feedback)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
if __name__ == "__main__":
|
|
430
|
+
import sys
|
|
431
|
+
|
|
432
|
+
if len(sys.argv) < 2:
|
|
433
|
+
print("Usage: learning.py <log|stats|patterns|reflect|advice> [args]")
|
|
434
|
+
sys.exit(1)
|
|
435
|
+
|
|
436
|
+
cmd = sys.argv[1]
|
|
437
|
+
|
|
438
|
+
if cmd == "log":
|
|
439
|
+
action = sys.argv[2] if len(sys.argv) > 2 else "test"
|
|
440
|
+
detail = sys.argv[3] if len(sys.argv) > 3 else "test action"
|
|
441
|
+
success = sys.argv[4].lower() == "true" if len(sys.argv) > 4 else True
|
|
442
|
+
print(learning_log(action, detail, success))
|
|
443
|
+
|
|
444
|
+
elif cmd == "stats":
|
|
445
|
+
days = int(sys.argv[2]) if len(sys.argv) > 2 else 7
|
|
446
|
+
print(json.dumps(learning_stats(days), indent=2))
|
|
447
|
+
|
|
448
|
+
elif cmd == "patterns":
|
|
449
|
+
print(json.dumps(learning_patterns(), indent=2))
|
|
450
|
+
|
|
451
|
+
elif cmd == "reflect":
|
|
452
|
+
period = sys.argv[2] if len(sys.argv) > 2 else "week"
|
|
453
|
+
print(json.dumps(learning_reflect(period), indent=2))
|
|
454
|
+
|
|
455
|
+
elif cmd == "advice":
|
|
456
|
+
action = sys.argv[2] if len(sys.argv) > 2 else ""
|
|
457
|
+
print(learning_advice(action))
|
|
458
|
+
|
|
459
|
+
else:
|
|
460
|
+
print(f"Unknown command: {cmd}")
|