ethan-agent 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.
- ethan_agent-0.1.0/.gitignore +58 -0
- ethan_agent-0.1.0/LICENSE +21 -0
- ethan_agent-0.1.0/PKG-INFO +533 -0
- ethan_agent-0.1.0/README.md +492 -0
- ethan_agent-0.1.0/README_CN.md +584 -0
- ethan_agent-0.1.0/ethan/__init__.py +3 -0
- ethan_agent-0.1.0/ethan/acp/__init__.py +126 -0
- ethan_agent-0.1.0/ethan/core/__init__.py +0 -0
- ethan_agent-0.1.0/ethan/core/agent.py +394 -0
- ethan_agent-0.1.0/ethan/core/config.py +231 -0
- ethan_agent-0.1.0/ethan/core/heartbeat.py +178 -0
- ethan_agent-0.1.0/ethan/core/onboarding.py +24 -0
- ethan_agent-0.1.0/ethan/defaults/skills/channels/SKILL.md +43 -0
- ethan_agent-0.1.0/ethan/defaults/skills/deepwiki/SKILL.md +47 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/SKILL.md +232 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-chat-create.md +162 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-chat-identity.md +55 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-chat-list.md +166 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-chat-messages-list.md +157 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-chat-search.md +142 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-chat-update.md +84 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-feed-group-list-item.md +68 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-feed-group-list.md +65 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-feed-group-query-item.md +44 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-feed-groups.md +452 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-feed-shortcut-create.md +97 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-feed-shortcut-list.md +103 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-feed-shortcut-remove.md +48 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-flag-cancel.md +67 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-flag-create.md +67 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-flag-list.md +100 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-message-enrichment.md +54 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-messages-mget.md +99 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-messages-reply.md +247 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-messages-resources-download.md +94 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-messages-search.md +234 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-messages-send.md +248 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-reactions.md +299 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-im/references/lark-im-threads-messages-list.md +115 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-shared/SKILL.md +168 -0
- ethan_agent-0.1.0/ethan/defaults/skills/lark-shared/references/lark-wiki-token-routing.md +42 -0
- ethan_agent-0.1.0/ethan/defaults/skills/skills-manager/SKILL.md +70 -0
- ethan_agent-0.1.0/ethan/defaults/system/agent.md +29 -0
- ethan_agent-0.1.0/ethan/defaults/system/heartbeat.md +0 -0
- ethan_agent-0.1.0/ethan/defaults/system/identity.md +3 -0
- ethan_agent-0.1.0/ethan/defaults/system/soul.md +26 -0
- ethan_agent-0.1.0/ethan/defaults/system/tools.md +7 -0
- ethan_agent-0.1.0/ethan/interface/__init__.py +0 -0
- ethan_agent-0.1.0/ethan/interface/__main__.py +3 -0
- ethan_agent-0.1.0/ethan/interface/api.py +56 -0
- ethan_agent-0.1.0/ethan/interface/cli.py +128 -0
- ethan_agent-0.1.0/ethan/interface/commands/__init__.py +0 -0
- ethan_agent-0.1.0/ethan/interface/commands/code.py +75 -0
- ethan_agent-0.1.0/ethan/interface/commands/knowledge.py +93 -0
- ethan_agent-0.1.0/ethan/interface/commands/model.py +93 -0
- ethan_agent-0.1.0/ethan/interface/commands/provider.py +63 -0
- ethan_agent-0.1.0/ethan/interface/commands/schedule.py +100 -0
- ethan_agent-0.1.0/ethan/interface/commands/session.py +101 -0
- ethan_agent-0.1.0/ethan/interface/commands/skill.py +91 -0
- ethan_agent-0.1.0/ethan/interface/commands/update.py +267 -0
- ethan_agent-0.1.0/ethan/interface/lark.py +207 -0
- ethan_agent-0.1.0/ethan/interface/lark_events.py +388 -0
- ethan_agent-0.1.0/ethan/interface/repl.py +596 -0
- ethan_agent-0.1.0/ethan/interface/routers/__init__.py +0 -0
- ethan_agent-0.1.0/ethan/interface/routers/chat.py +287 -0
- ethan_agent-0.1.0/ethan/interface/routers/completions.py +185 -0
- ethan_agent-0.1.0/ethan/interface/routers/deps.py +55 -0
- ethan_agent-0.1.0/ethan/interface/routers/docs.py +46 -0
- ethan_agent-0.1.0/ethan/interface/routers/knowledge.py +76 -0
- ethan_agent-0.1.0/ethan/interface/routers/logs.py +22 -0
- ethan_agent-0.1.0/ethan/interface/routers/memory.py +80 -0
- ethan_agent-0.1.0/ethan/interface/routers/schedule.py +91 -0
- ethan_agent-0.1.0/ethan/interface/routers/sessions.py +115 -0
- ethan_agent-0.1.0/ethan/interface/routers/settings.py +256 -0
- ethan_agent-0.1.0/ethan/interface/routers/skills.py +57 -0
- ethan_agent-0.1.0/ethan/knowledge/__init__.py +0 -0
- ethan_agent-0.1.0/ethan/knowledge/base.py +177 -0
- ethan_agent-0.1.0/ethan/memory/__init__.py +0 -0
- ethan_agent-0.1.0/ethan/memory/api_keys.py +84 -0
- ethan_agent-0.1.0/ethan/memory/consolidator.py +139 -0
- ethan_agent-0.1.0/ethan/memory/embeddings.py +89 -0
- ethan_agent-0.1.0/ethan/memory/episodic.py +96 -0
- ethan_agent-0.1.0/ethan/memory/facts.py +161 -0
- ethan_agent-0.1.0/ethan/memory/persistent.py +22 -0
- ethan_agent-0.1.0/ethan/memory/procedures.py +72 -0
- ethan_agent-0.1.0/ethan/memory/session.py +274 -0
- ethan_agent-0.1.0/ethan/memory/vector_store.py +161 -0
- ethan_agent-0.1.0/ethan/memory/working.py +120 -0
- ethan_agent-0.1.0/ethan/providers/__init__.py +0 -0
- ethan_agent-0.1.0/ethan/providers/anthropic.py +196 -0
- ethan_agent-0.1.0/ethan/providers/base.py +71 -0
- ethan_agent-0.1.0/ethan/providers/manager.py +33 -0
- ethan_agent-0.1.0/ethan/providers/openai_compat.py +186 -0
- ethan_agent-0.1.0/ethan/scheduler/__init__.py +0 -0
- ethan_agent-0.1.0/ethan/scheduler/cron.py +111 -0
- ethan_agent-0.1.0/ethan/scheduler/heartbeat.py +41 -0
- ethan_agent-0.1.0/ethan/skills/__init__.py +0 -0
- ethan_agent-0.1.0/ethan/skills/generator.py +94 -0
- ethan_agent-0.1.0/ethan/skills/loader.py +100 -0
- ethan_agent-0.1.0/ethan/skills/registry.py +66 -0
- ethan_agent-0.1.0/ethan/skills/stats.py +46 -0
- ethan_agent-0.1.0/ethan/skills/updater.py +82 -0
- ethan_agent-0.1.0/ethan/tools/__init__.py +0 -0
- ethan_agent-0.1.0/ethan/tools/base.py +39 -0
- ethan_agent-0.1.0/ethan/tools/builtin/__init__.py +0 -0
- ethan_agent-0.1.0/ethan/tools/builtin/acp.py +37 -0
- ethan_agent-0.1.0/ethan/tools/builtin/file.py +121 -0
- ethan_agent-0.1.0/ethan/tools/builtin/knowledge.py +49 -0
- ethan_agent-0.1.0/ethan/tools/builtin/memory_write.py +36 -0
- ethan_agent-0.1.0/ethan/tools/builtin/procedure_write.py +32 -0
- ethan_agent-0.1.0/ethan/tools/builtin/profile_update.py +128 -0
- ethan_agent-0.1.0/ethan/tools/builtin/schedule.py +163 -0
- ethan_agent-0.1.0/ethan/tools/builtin/search.py +110 -0
- ethan_agent-0.1.0/ethan/tools/builtin/shell.py +40 -0
- ethan_agent-0.1.0/ethan/tools/builtin/skill_create.py +57 -0
- ethan_agent-0.1.0/ethan/tools/builtin/web.py +55 -0
- ethan_agent-0.1.0/ethan/tools/builtin/web_search.py +78 -0
- ethan_agent-0.1.0/ethan/tools/mcp_client.py +85 -0
- ethan_agent-0.1.0/ethan/tools/registry.py +71 -0
- ethan_agent-0.1.0/ethan/tools/result_compressor.py +41 -0
- ethan_agent-0.1.0/pyproject.toml +68 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
wheels/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
.venv/
|
|
14
|
+
env/
|
|
15
|
+
|
|
16
|
+
# IDE
|
|
17
|
+
.idea/
|
|
18
|
+
.vscode/
|
|
19
|
+
*.swp
|
|
20
|
+
*.swo
|
|
21
|
+
*~
|
|
22
|
+
|
|
23
|
+
# OS
|
|
24
|
+
.DS_Store
|
|
25
|
+
Thumbs.db
|
|
26
|
+
|
|
27
|
+
# Environment & secrets
|
|
28
|
+
.env
|
|
29
|
+
.env.local
|
|
30
|
+
.env.production
|
|
31
|
+
!.env.example
|
|
32
|
+
|
|
33
|
+
# Project data (user-specific)
|
|
34
|
+
*.db
|
|
35
|
+
|
|
36
|
+
# Testing
|
|
37
|
+
.coverage
|
|
38
|
+
htmlcov/
|
|
39
|
+
.pytest_cache/
|
|
40
|
+
|
|
41
|
+
# Distribution
|
|
42
|
+
*.tar.gz
|
|
43
|
+
*.whl
|
|
44
|
+
|
|
45
|
+
# Claude Code artifacts
|
|
46
|
+
.agents/
|
|
47
|
+
skills-lock.json
|
|
48
|
+
.claude/
|
|
49
|
+
.run/
|
|
50
|
+
plan/
|
|
51
|
+
|
|
52
|
+
# Local bin scripts
|
|
53
|
+
bin/
|
|
54
|
+
|
|
55
|
+
# Local personal notes
|
|
56
|
+
TODO.md
|
|
57
|
+
|
|
58
|
+
pypi/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ethan Agent 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.
|
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ethan-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Ethan — Lightweight personal AI agent with memory, skills, and multi-model support
|
|
5
|
+
Project-URL: Homepage, https://github.com/llm011/ethan-agent
|
|
6
|
+
Project-URL: Repository, https://github.com/llm011/ethan-agent
|
|
7
|
+
Project-URL: Issues, https://github.com/llm011/ethan-agent/issues
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agent,ai,assistant,claude,gemini,llm,memory,skills
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Requires-Dist: aiosqlite>=0.22.1
|
|
22
|
+
Requires-Dist: anthropic>=0.109.1
|
|
23
|
+
Requires-Dist: apscheduler>=3.11.2
|
|
24
|
+
Requires-Dist: fastapi>=0.136.3
|
|
25
|
+
Requires-Dist: lark-oapi>=1.6.8
|
|
26
|
+
Requires-Dist: mcp>=1.27.2
|
|
27
|
+
Requires-Dist: openai>=2.41.1
|
|
28
|
+
Requires-Dist: pexpect>=4.9.0
|
|
29
|
+
Requires-Dist: prompt-toolkit>=3.0.52
|
|
30
|
+
Requires-Dist: pydantic-settings>=2.14.1
|
|
31
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
32
|
+
Requires-Dist: python-multipart>=0.0.32
|
|
33
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
34
|
+
Requires-Dist: rich>=15.0.0
|
|
35
|
+
Requires-Dist: sqlalchemy>=2.0.50
|
|
36
|
+
Requires-Dist: sqlite-vec>=0.1.9
|
|
37
|
+
Requires-Dist: typer>=0.26.7
|
|
38
|
+
Requires-Dist: uvicorn>=0.49.0
|
|
39
|
+
Requires-Dist: uvloop>=0.22.1
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# Ethan Agent
|
|
43
|
+
|
|
44
|
+
[中文文档](./README_CN.md)
|
|
45
|
+
|
|
46
|
+
A lightweight, extensible personal AI agent built in Python. Designed to run persistently on your own hardware with memory that grows over time, scheduled tasks, and a pluggable tool/skill system.
|
|
47
|
+
|
|
48
|
+
Ethan combines ideas from [OpenClaw](https://github.com/openclaw/openclaw) (structured agent loop, layered memory), [Hermes Agent](https://github.com/NousResearch/hermes-agent) (self-improving skills, memory consolidation), and [nanobot](https://github.com/HKUDS/nanobot) (minimal core, readable codebase).
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
**Memory system (five layers)**
|
|
55
|
+
- Hot/warm/cold three-tier sliding window for long-conversation context; older content auto-compressed by a cheap model
|
|
56
|
+
- Structured Facts: confidence-scored entries with conflict detection and deduplication (`~/.ethan/memory/facts.json`)
|
|
57
|
+
- Behavioral Procedures: learned from user corrections, loaded every conversation (`procedures.json`)
|
|
58
|
+
- Session Episodes: auto-summarized on exit, supports keyword search (`episodes.json`)
|
|
59
|
+
- User Profile: narrative document storing personal phrases, goals, and agent agreements (`user_profile.md`)
|
|
60
|
+
- **Proactive memory write**: Agent calls tools mid-conversation to instantly persist anything worth remembering — no waiting for batch processing
|
|
61
|
+
|
|
62
|
+
**Skill system**
|
|
63
|
+
- Keyword trigger matching, auto-injected into system prompt
|
|
64
|
+
- `fast_path: true` routes matched input to the millisecond fast track
|
|
65
|
+
- `channels: [lark, web]` filters skills by channel so each surface gets only relevant skills
|
|
66
|
+
- Hit tracking and correction collection; Heartbeat auto-updates skill content with a cheap model when corrections accumulate
|
|
67
|
+
- Agent can create new skills mid-conversation via the `skill_create` tool
|
|
68
|
+
|
|
69
|
+
**Three-track routing**
|
|
70
|
+
- **fast**: short commands + keyword match → minimal prompt + fast_path tools only + 2 iterations
|
|
71
|
+
- **medium**: mid-length messages → full prompt + all tools + 4 iterations
|
|
72
|
+
- **full**: complex tasks → full prompt + all tools + 10 iterations
|
|
73
|
+
|
|
74
|
+
**Scheduler**
|
|
75
|
+
- Create cron or interval jobs in conversation; SQLite-persisted, survives restarts
|
|
76
|
+
- `heartbeat.md`: write natural-language tasks; the system runs them periodically
|
|
77
|
+
|
|
78
|
+
**Tool system**
|
|
79
|
+
- Shell execution, web search (DuckDuckGo), web fetch, file I/O, knowledge base
|
|
80
|
+
- Tool results over 4 000 chars are auto-summarized by a cheap model before going back to the main model
|
|
81
|
+
- Identical calls within the same turn hit an in-memory cache — no duplicate execution
|
|
82
|
+
|
|
83
|
+
**Prompt Caching**
|
|
84
|
+
- System prompt split into stable layer / dynamic layer; stable layer cached 5 min, token cost drops to 0.1×
|
|
85
|
+
|
|
86
|
+
**Multi-channel**
|
|
87
|
+
- CLI REPL, Web UI (Next.js), Lark/Feishu (WebSocket, no public IP required)
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Quick Start (Docker, recommended)
|
|
92
|
+
|
|
93
|
+
Docker is the easiest deployment path — backend and Web UI run as separate containers, data persisted to a local volume.
|
|
94
|
+
|
|
95
|
+
### Prerequisites
|
|
96
|
+
|
|
97
|
+
- Docker 20.10+
|
|
98
|
+
- Docker Compose v2
|
|
99
|
+
|
|
100
|
+
### 1. Clone
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
git clone https://github.com/llm011/ethan-agent.git
|
|
104
|
+
cd ethan-agent
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 2. Configure
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
cp deploy/.env.example deploy/.env
|
|
111
|
+
# Edit deploy/.env with your API keys
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
At least one provider is required:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Anthropic (recommended — supports Prompt Caching)
|
|
118
|
+
ANTHROPIC_API_KEY=sk-ant-xxx
|
|
119
|
+
|
|
120
|
+
# Or any OpenAI-compatible API
|
|
121
|
+
OPENAI_API_KEY=sk-xxx
|
|
122
|
+
OPENAI_BASE_URL=https://api.example.com/v1
|
|
123
|
+
|
|
124
|
+
AGENT_DEFAULT_MODEL=claude-sonnet-4-6
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 3. Build and start
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
cd deploy
|
|
131
|
+
docker compose up -d --build
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
First build takes ~3–5 minutes (installing deps + building Next.js).
|
|
135
|
+
|
|
136
|
+
### 4. Access
|
|
137
|
+
|
|
138
|
+
- **Web UI**: http://localhost:3000
|
|
139
|
+
- **API**: http://localhost:8900
|
|
140
|
+
- **Health check**: http://localhost:8900/health
|
|
141
|
+
|
|
142
|
+
### 5. Common commands
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
docker compose logs -f ethan # tail logs
|
|
146
|
+
docker compose restart ethan # restart backend
|
|
147
|
+
docker compose down # stop
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Local Development
|
|
153
|
+
|
|
154
|
+
### Prerequisites
|
|
155
|
+
|
|
156
|
+
- Python 3.12+
|
|
157
|
+
- [uv](https://docs.astral.sh/uv/) package manager
|
|
158
|
+
- Node.js 20+ (Web UI)
|
|
159
|
+
|
|
160
|
+
### Install
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
git clone https://github.com/llm011/ethan-agent.git
|
|
164
|
+
cd ethan-agent
|
|
165
|
+
uv sync
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Configure
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
cp .env.example .env
|
|
172
|
+
# Edit .env with your API keys
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Or via CLI:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
ethan provider set anthropic --api-key sk-ant-xxx
|
|
179
|
+
ethan model default claude-sonnet-4-6
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Run
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
# Interactive REPL
|
|
186
|
+
uv run python -m ethan.interface.cli
|
|
187
|
+
|
|
188
|
+
# Single-turn query
|
|
189
|
+
uv run python -m ethan.interface.cli -p "What's the weather in Tokyo?"
|
|
190
|
+
|
|
191
|
+
# Specify model
|
|
192
|
+
uv run python -m ethan.interface.cli -m claude-sonnet-4-6
|
|
193
|
+
|
|
194
|
+
# Resume last session
|
|
195
|
+
uv run python -m ethan.interface.cli -r last
|
|
196
|
+
|
|
197
|
+
# Start HTTP API server
|
|
198
|
+
uv run python -m ethan.interface.cli serve
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Install globally (optional)
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
chmod +x bin/ethan
|
|
205
|
+
ln -s $(pwd)/bin/ethan ~/bin/ethan
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Web UI (dev mode)
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
cd web
|
|
212
|
+
npm install
|
|
213
|
+
npm run dev # http://localhost:3000
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### macOS auto-start (launchd)
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
./deploy/install.sh
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Architecture
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
ethan/
|
|
228
|
+
├── core/
|
|
229
|
+
│ ├── agent.py # ReAct loop, three-track router (fast/medium/full)
|
|
230
|
+
│ ├── config.py # YAML config (~/.ethan/config.yaml)
|
|
231
|
+
│ └── heartbeat.py # Heartbeat system, periodic maintenance
|
|
232
|
+
├── providers/
|
|
233
|
+
│ ├── base.py # Unified interface (Message, ToolCall, BaseProvider)
|
|
234
|
+
│ ├── anthropic.py # Claude native protocol + Prompt Caching
|
|
235
|
+
│ ├── openai_compat.py # OpenAI-compatible protocol
|
|
236
|
+
│ └── manager.py # Route model ID → provider
|
|
237
|
+
├── memory/
|
|
238
|
+
│ ├── session.py # Session persistence (SQLite)
|
|
239
|
+
│ ├── working.py # Three-tier sliding window memory
|
|
240
|
+
│ ├── facts.py # Structured Facts (conflict detection + confidence)
|
|
241
|
+
│ ├── procedures.py # Behavioral rules (learned from corrections)
|
|
242
|
+
│ ├── episodic.py # Session episode archive
|
|
243
|
+
│ └── consolidator.py # Compress with cheap model
|
|
244
|
+
├── skills/
|
|
245
|
+
│ ├── loader.py # Load skills (directory format + legacy .md)
|
|
246
|
+
│ ├── registry.py # Match (with channel filter) + hit stats
|
|
247
|
+
│ ├── stats.py # Hit count + correction collection
|
|
248
|
+
│ ├── updater.py # Auto-update skill content via cheap model
|
|
249
|
+
│ └── generator.py # Auto-generate skills from sessions
|
|
250
|
+
├── tools/
|
|
251
|
+
│ ├── base.py # BaseTool abstract class
|
|
252
|
+
│ ├── registry.py # Registry + concurrent executor + turn cache
|
|
253
|
+
│ ├── result_compressor.py # Auto-summarize long tool output
|
|
254
|
+
│ └── builtin/
|
|
255
|
+
│ ├── shell.py # Execute shell commands
|
|
256
|
+
│ ├── web_search.py # DuckDuckGo search
|
|
257
|
+
│ ├── web.py # Fetch & extract web page text
|
|
258
|
+
│ ├── file.py # File read/write/list
|
|
259
|
+
│ ├── memory_write.py # Proactive fact write
|
|
260
|
+
│ ├── procedure_write.py # Proactive procedure write
|
|
261
|
+
│ ├── profile_update.py # Update user profile
|
|
262
|
+
│ └── skill_create.py # Create skill mid-conversation
|
|
263
|
+
├── scheduler/
|
|
264
|
+
│ └── cron.py # APScheduler with SQLite persistence
|
|
265
|
+
└── interface/
|
|
266
|
+
├── cli.py # Typer CLI entry point
|
|
267
|
+
├── repl.py # Interactive REPL with prompt_toolkit
|
|
268
|
+
├── api.py # FastAPI HTTP + SSE streaming
|
|
269
|
+
├── lark_events.py # Lark WebSocket
|
|
270
|
+
└── commands/ # Subcommands (model, provider, session, skill, schedule)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Memory System
|
|
276
|
+
|
|
277
|
+
Ethan uses a five-layer memory architecture:
|
|
278
|
+
|
|
279
|
+
| Layer | Content | Storage |
|
|
280
|
+
|-------|---------|---------|
|
|
281
|
+
| Hot | Last N turns (full messages) | In-memory |
|
|
282
|
+
| Warm | Rolling summary of older turns | In-memory |
|
|
283
|
+
| Cold (Facts) | Key facts extracted across sessions | `~/.ethan/memory/facts.json` |
|
|
284
|
+
| Procedures | Behavioral rules learned from corrections | `~/.ethan/memory/procedures.json` |
|
|
285
|
+
| User Profile | Narrative personal context (goals, phrases, agreements) | `~/.ethan/memory/user_profile.md` |
|
|
286
|
+
|
|
287
|
+
Compression is **batched** (not per-turn) and uses an automatically inferred cheap model (e.g. Haiku for Claude users, Flash Lite for Gemini users).
|
|
288
|
+
|
|
289
|
+
Agent proactively writes to all layers mid-conversation via `memory_write`, `procedure_write`, and `profile_update` tools — no waiting for the next compression cycle.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Skills
|
|
294
|
+
|
|
295
|
+
Skills are Markdown files loaded from two sources, in priority order:
|
|
296
|
+
|
|
297
|
+
1. **Built-in skills** — `ethan/skills/<name>/SKILL.md` (shipped with the project)
|
|
298
|
+
2. **User skills** — `~/.ethan/skills/<name>/SKILL.md` or `~/.ethan/skills/<name>.md`
|
|
299
|
+
|
|
300
|
+
Both support a directory format (`<name>/SKILL.md` + `references/`) and the legacy single-file `.md` format.
|
|
301
|
+
|
|
302
|
+
```markdown
|
|
303
|
+
---
|
|
304
|
+
name: deploy-checklist
|
|
305
|
+
trigger: deploy|ship|release
|
|
306
|
+
description: Pre-deployment checklist
|
|
307
|
+
fast_path: true # route to fast track when triggered
|
|
308
|
+
channels: # empty = all channels; list = restrict
|
|
309
|
+
- web
|
|
310
|
+
version: "1.0"
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
Steps before deploying:
|
|
314
|
+
1. Run tests
|
|
315
|
+
2. Check for uncommitted changes
|
|
316
|
+
3. ...
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
When a user message matches a skill's `trigger`, the skill content is injected into the system prompt. Built-in skills include `channels`, `lark-im`, and `home-assistant`.
|
|
320
|
+
|
|
321
|
+
Skills accumulate hit stats and user corrections. When corrections reach a threshold (default: 2), the Heartbeat job merges them into the skill file using a cheap model.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Tools
|
|
326
|
+
|
|
327
|
+
Tools are pluggable — add a new one without touching the agent loop:
|
|
328
|
+
|
|
329
|
+
```python
|
|
330
|
+
from ethan.tools.base import BaseTool
|
|
331
|
+
|
|
332
|
+
class MyTool(BaseTool):
|
|
333
|
+
name = "my_tool"
|
|
334
|
+
description = "Does something useful"
|
|
335
|
+
fast_path = False # set True to make available in fast-track mode
|
|
336
|
+
cacheable = False # set True to cache identical calls within a turn
|
|
337
|
+
parameters = {"type": "object", "properties": {...}, "required": [...]}
|
|
338
|
+
|
|
339
|
+
async def run(self, **kwargs) -> str:
|
|
340
|
+
return "result"
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Register it in `cli.py` and the LLM will automatically use it when relevant.
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## CLI Commands
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
ethan Start interactive REPL
|
|
351
|
+
ethan -p "..." Single-turn query
|
|
352
|
+
ethan -m MODEL Use specific model
|
|
353
|
+
ethan -r last Resume last session
|
|
354
|
+
ethan serve Start HTTP API server
|
|
355
|
+
|
|
356
|
+
ethan model list|add|remove|default
|
|
357
|
+
ethan provider list|set
|
|
358
|
+
ethan session list|show|delete
|
|
359
|
+
ethan skill list|show|create
|
|
360
|
+
ethan schedule list|remove|pause|resume
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## HTTP API
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
GET /health # Health check
|
|
369
|
+
GET /models # Available models
|
|
370
|
+
POST /chat # Chat (stream: true for SSE)
|
|
371
|
+
GET /sessions # Session list
|
|
372
|
+
GET /sessions/{id} # Session detail + messages
|
|
373
|
+
GET /memory/facts # Facts list
|
|
374
|
+
GET /memory/episodes # Episode summaries
|
|
375
|
+
GET /skills # Skill list
|
|
376
|
+
POST /skills # Create skill
|
|
377
|
+
POST /skills/evolve # Trigger skill auto-update
|
|
378
|
+
GET /schedule # Scheduled jobs
|
|
379
|
+
GET /system-prompt-preview # Current system prompt preview
|
|
380
|
+
GET /channels # Channel list
|
|
381
|
+
GET /knowledge/search # Semantic search
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## Configuration
|
|
387
|
+
|
|
388
|
+
All config lives in `~/.ethan/config.yaml`:
|
|
389
|
+
|
|
390
|
+
```yaml
|
|
391
|
+
providers:
|
|
392
|
+
anthropic:
|
|
393
|
+
api_key: sk-ant-xxx
|
|
394
|
+
base_url: https://api.anthropic.com # optional
|
|
395
|
+
proxy: null # per-provider proxy
|
|
396
|
+
openai_compat:
|
|
397
|
+
api_key: sk-xxx
|
|
398
|
+
base_url: https://api.openai.com/v1
|
|
399
|
+
|
|
400
|
+
models:
|
|
401
|
+
- id: claude-sonnet-4-6
|
|
402
|
+
provider: anthropic
|
|
403
|
+
description: Claude Sonnet 4.6
|
|
404
|
+
alias: [sonnet]
|
|
405
|
+
- id: gpt-4o
|
|
406
|
+
provider: openai_compat
|
|
407
|
+
alias: [gpt]
|
|
408
|
+
|
|
409
|
+
network:
|
|
410
|
+
proxy: http://127.0.0.1:7890 # global proxy
|
|
411
|
+
|
|
412
|
+
defaults:
|
|
413
|
+
model: claude-sonnet-4-6
|
|
414
|
+
agent_name: Ethan
|
|
415
|
+
max_tokens: 4096
|
|
416
|
+
max_tool_iterations: 10
|
|
417
|
+
routing:
|
|
418
|
+
fast_max_length: 12
|
|
419
|
+
medium_max_length: 80
|
|
420
|
+
medium_max_iters: 4
|
|
421
|
+
fast_keywords:
|
|
422
|
+
- "turn off*light"
|
|
423
|
+
- "play music"
|
|
424
|
+
fast_skill_triggers:
|
|
425
|
+
- "home assistant"
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Environment variables in `.env` override config values (useful for secrets).
|
|
429
|
+
|
|
430
|
+
### Config directory layout
|
|
431
|
+
|
|
432
|
+
```
|
|
433
|
+
~/.ethan/
|
|
434
|
+
├── config.yaml # Main config (providers, models, routing)
|
|
435
|
+
├── system/
|
|
436
|
+
│ ├── identity.md # Agent identity (name, role)
|
|
437
|
+
│ ├── soul.md # Behavioral principles
|
|
438
|
+
│ └── heartbeat.md # Heartbeat tasks (natural language)
|
|
439
|
+
├── memory/
|
|
440
|
+
│ ├── facts.json # Structured facts
|
|
441
|
+
│ ├── procedures.json # Behavioral rules
|
|
442
|
+
│ ├── episodes.json # Session episode archive
|
|
443
|
+
│ └── user_profile.md # User profile (narrative)
|
|
444
|
+
├── skills/ # User-defined skills
|
|
445
|
+
│ └── <name>/
|
|
446
|
+
│ └── SKILL.md
|
|
447
|
+
└── sessions.db # Session history (SQLite)
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Roadmap
|
|
453
|
+
|
|
454
|
+
### ✅ Completed
|
|
455
|
+
|
|
456
|
+
**Core Agent**
|
|
457
|
+
- [x] Multi-model provider (Anthropic + OpenAI-compatible: Gemini, GPT, Ollama, etc.)
|
|
458
|
+
- [x] ReAct agent loop with streaming output
|
|
459
|
+
- [x] Three-track router: fast / medium / full, tool result compression, per-turn dedup cache
|
|
460
|
+
- [x] Prompt Caching (Anthropic stable-prefix cache_control, ~0.1× input cost)
|
|
461
|
+
|
|
462
|
+
**Five-Layer Memory**
|
|
463
|
+
- [x] Hot/warm/cold sliding window + cheap-model batch compression
|
|
464
|
+
- [x] Structured Facts (confidence scoring + conflict detection)
|
|
465
|
+
- [x] Behavioral Procedures (learned from user corrections)
|
|
466
|
+
- [x] Session Episode archive (auto-summary, keyword search)
|
|
467
|
+
- [x] User Profile — narrative document with five named sections
|
|
468
|
+
- [x] Proactive memory write: `memory_write`, `procedure_write`, `profile_update`, `skill_create`
|
|
469
|
+
- [x] Memory context isolation (anti-pollution XML tags)
|
|
470
|
+
|
|
471
|
+
**Skill System**
|
|
472
|
+
- [x] Dual-source loading (built-in + user-defined) + channel filter (`channels` field)
|
|
473
|
+
- [x] `fast_path` opt-in, hit stats, correction collection, auto-update (Updater)
|
|
474
|
+
- [x] Session-end background Skill generation (Hermes-style)
|
|
475
|
+
- [x] Built-in skills: home-assistant, lark-im, channels, deepwiki
|
|
476
|
+
|
|
477
|
+
**Tools**
|
|
478
|
+
- [x] shell, web_search, web_fetch, file_read/write/list, rg, fd
|
|
479
|
+
- [x] Knowledge base (sqlite-vec semantic search), scheduler tools, ACP → Claude Code
|
|
480
|
+
|
|
481
|
+
**Scheduler**
|
|
482
|
+
- [x] Cron + interval, SQLite persistence, auto-restore on restart
|
|
483
|
+
- [x] `heartbeat.md`: natural-language periodic tasks executed automatically
|
|
484
|
+
|
|
485
|
+
**Channels & API**
|
|
486
|
+
- [x] Web UI (Next.js): chat timeline, memory, skills, schedule, knowledge, settings
|
|
487
|
+
- [x] Tool call timeline (collapsible, with icons + duration)
|
|
488
|
+
- [x] Feishu/Lark WebSocket (no public IP required)
|
|
489
|
+
- [x] OpenAI-compatible Completions API (`/v1/chat/completions`) + API key management
|
|
490
|
+
- [x] Docker deployment + macOS launchd auto-start
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
### 🚀 Planned
|
|
495
|
+
|
|
496
|
+
**UX Improvements**
|
|
497
|
+
- [ ] **Message quoting**: quote a previous message in the input box
|
|
498
|
+
- [ ] **User profile settings**: avatar upload, display name shown in chat bubbles
|
|
499
|
+
- [ ] **Scheduler suggestions**: Agent detects scheduling opportunities in conversation and prompts user with 1-2-3 options
|
|
500
|
+
- [ ] **Scheduler templates**: ready-to-use tasks (daily briefing, HA device check, knowledge digest)
|
|
501
|
+
|
|
502
|
+
**Channel Expansion**
|
|
503
|
+
- [ ] **WeCom (Enterprise WeChat)**: alongside Feishu as a second messaging channel
|
|
504
|
+
- [ ] **Mobile UI**: bottom tab nav, touch gestures, keyboard inset handling
|
|
505
|
+
|
|
506
|
+
**Coding Agent Integration**
|
|
507
|
+
- [ ] **ACP multi-turn optimization**: smoother Claude Code / OpenCode / Codex sessions with collapsible tool traces
|
|
508
|
+
- [ ] **MCP client**: connect external MCP servers, auto-register tools
|
|
509
|
+
|
|
510
|
+
**Long-term**
|
|
511
|
+
- [ ] **Space isolation**: separate memory/skills per context (life / work / project)
|
|
512
|
+
- [ ] **Async interrupt**: detect new messages during long tasks, respond between tool calls
|
|
513
|
+
- [ ] **Obsidian integration**: read/write Obsidian vault as knowledge base
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Documentation
|
|
518
|
+
|
|
519
|
+
Detailed design docs for each module are in [`docs/`](./docs/):
|
|
520
|
+
|
|
521
|
+
- [Architecture Overview](docs/architecture.md)
|
|
522
|
+
- [Agent Loop](docs/agent-loop.md)
|
|
523
|
+
- [Routing](docs/routing.md)
|
|
524
|
+
- [Provider Layer](docs/providers.md)
|
|
525
|
+
- [Tool System](docs/tools.md)
|
|
526
|
+
- [Memory System](docs/memory.md)
|
|
527
|
+
- [Skill System](docs/skills.md)
|
|
528
|
+
- [Scheduler](docs/scheduler.md)
|
|
529
|
+
- [Interface Layer](docs/interface.md)
|
|
530
|
+
|
|
531
|
+
## License
|
|
532
|
+
|
|
533
|
+
MIT
|