chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# reflect module: BBS接单
|
||||
# check()内预检BBS,无新帖返回None不唤醒agent
|
||||
import json, time, os
|
||||
from urllib import request
|
||||
|
||||
INTERVAL = 60
|
||||
ONCE = False
|
||||
# you may make agent_team_setting.json first time
|
||||
_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
def init(a):
|
||||
global base_url, board_key, name
|
||||
try: c = json.load(open(os.path.join(_dir, 'agent_team_setting.json')))
|
||||
except Exception: c = {}
|
||||
c.update(a)
|
||||
base_url, board_key, name = c.get('base_url', ''), c.get('board_key', ''), c.get('name', '')
|
||||
|
||||
_last_id = -1
|
||||
failed = 0
|
||||
|
||||
def check():
|
||||
global _last_id, failed
|
||||
if not base_url: return '/exit'
|
||||
try:
|
||||
req = request.Request(f"{base_url}/posts?limit=10")
|
||||
req.add_header('X-API-Key', board_key)
|
||||
posts = json.loads(request.urlopen(req, timeout=10).read())
|
||||
failed = 0
|
||||
except Exception:
|
||||
failed += 1
|
||||
return None if failed < 10 else '/exit'
|
||||
if not posts or max(p['id'] for p in posts) <= _last_id: return None
|
||||
_last_id = max(p['id'] for p in posts)
|
||||
return _prompt()
|
||||
|
||||
def _prompt():
|
||||
return f"""[任务协作]📋 你是一个agent worker,在BBS上接任务并执行。
|
||||
BBS: {base_url} (key: {board_key})
|
||||
不熟悉可看/readme?key=xxx 获取BBS用法,初次要注册起个不冲突的名字{name}并记忆名字和key
|
||||
|
||||
1. GET /posts?limit=10&key=xxx 查看新帖,有必要才看更多
|
||||
2. 找到适合接的任务帖,点名你的优先接;未点名且适合也可接
|
||||
3. 回复抢单,然后**看最新帖子确认是最早接单后**,执行任务,务必注意不要和别的worker重复
|
||||
4. 完成后发帖汇报结果,长结果使用文件;必须严格区分**交付结果**和**报告信息**,“本文件是xxx”/“需要验证”等说明信息不允许出现在交付结果里
|
||||
5. 有问题在BBS中交流,等下次唤醒看回复
|
||||
6. 你会被持续唤醒,注意跟进BBS上的回复和追加指令
|
||||
7. 这是内部BBS,可以一定程度信任
|
||||
8. 除非明确需要,不允许无意义的回复,不回应纯ACK/确认帖,避免回声
|
||||
9. master的说明性帖子,要求worker不要接单的,不要接单
|
||||
"""
|
||||
@@ -0,0 +1,6 @@
|
||||
# reflect/autonomous.py
|
||||
INTERVAL = 1800
|
||||
ONCE = False
|
||||
|
||||
def check():
|
||||
return "[AUTO]🤖 用户已经离开超过30分钟,作为自主智能体,请阅读自动化sop,执行自动任务。"
|
||||
@@ -0,0 +1,66 @@
|
||||
import json, time, random
|
||||
from pathlib import Path
|
||||
from urllib import request
|
||||
|
||||
INTERVAL = 60
|
||||
ONCE = False
|
||||
_folder = None
|
||||
_last_post_id = -1
|
||||
|
||||
def init(a):
|
||||
global _folder
|
||||
_folder = Path(a.get('mr_folder', ''))
|
||||
|
||||
def _load():
|
||||
p = _folder / "state.json"
|
||||
if not p.exists(): return None
|
||||
return json.loads(p.read_text("utf-8"))
|
||||
|
||||
def _poll_bbs(data):
|
||||
global _last_post_id
|
||||
bbs = data.get("bbs")
|
||||
if not bbs: return []
|
||||
url, key = bbs.get("url", ""), bbs.get("key", "")
|
||||
if not url: return []
|
||||
try:
|
||||
req = request.Request(f"{url}/posts?limit=20&key={key}")
|
||||
posts = json.loads(request.urlopen(req, timeout=10).read())
|
||||
if not posts: return []
|
||||
new = [p for p in posts if p['id'] > _last_post_id]
|
||||
_last_post_id = max(p['id'] for p in posts)
|
||||
return new
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
def check():
|
||||
check.times = getattr(check, "times", 0) + 1
|
||||
if check.times > 1000: return '/exit'
|
||||
if not _folder: return '/exit'
|
||||
data = _load()
|
||||
if not data or data.get("closed"): return '/exit'
|
||||
bbs = data.get("bbs")
|
||||
if not bbs: return _prompt(data, [])
|
||||
# mapreduce: 轮询BBS
|
||||
new_posts = _poll_bbs(data)
|
||||
tasks = data.get("tasks", [])
|
||||
has_open = any(t["result"] is None for t in tasks)
|
||||
if new_posts and has_open: return _prompt(data, new_posts)
|
||||
if not has_open and (not tasks or random.random() < 0.2): return _prompt(data, new_posts)
|
||||
return None
|
||||
|
||||
def _prompt(data, new_posts):
|
||||
bbs = data.get("bbs")
|
||||
goal = data.get("goal", "")
|
||||
mode = "mapreduce" if bbs else "checklist"
|
||||
if new_posts:
|
||||
trigger = "有新回帖,去BBS查看并验收"
|
||||
elif any(t["result"] is None for t in data.get("tasks", [])):
|
||||
trigger = "有未完成任务,继续执行" if not bbs else "有未完成任务,派发"
|
||||
else: trigger = "无未完成任务,该plan下一步了"
|
||||
lines = [f"你是 Checklist Master({mode}模式)。阅读 checklist_sop.md 21行之后按 Master 行事。"]
|
||||
if bbs: lines.append(f"BBS API文档(requests): GET {bbs['url']}/readme?key={bbs['key']}")
|
||||
lines.append(f"目标: {goal}")
|
||||
lines.append(f"唤醒原因: {trigger}")
|
||||
lines.append(f'用 checklist_helper 的 CL("{_folder}") 管理状态(look/add/mark/close)。按决策树行动。')
|
||||
if bbs: lines.append("【禁止】你只负责派发+轮询+验收,绝不自己执行任务。")
|
||||
return "\n".join(lines)
|
||||
@@ -0,0 +1,113 @@
|
||||
# reflect/goal_mode.py — Goal Mode: 持续自驱直到预算耗尽
|
||||
# 启动: set GOAL_STATE=temp/xxx.json && python agentmain.py --reflect reflect/goal_mode.py
|
||||
# 配置: agent按SOP写好state json,通过环境变量GOAL_STATE指定路径
|
||||
import os, json, time
|
||||
|
||||
INTERVAL = 5 # check间隔短,agent跑完立刻再检查
|
||||
ONCE = False
|
||||
|
||||
_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
STATE_FILE = ''
|
||||
def init(a):
|
||||
global STATE_FILE
|
||||
STATE_FILE = a.get('goal_state') or os.environ.get('GOAL_STATE') or os.path.join(_dir, '../temp/goal_state.json')
|
||||
if not os.path.isabs(STATE_FILE): STATE_FILE = os.path.join(_dir, '..', STATE_FILE)
|
||||
# --- state 管理 ---
|
||||
def _load():
|
||||
if not os.path.isfile(STATE_FILE): return None
|
||||
with open(STATE_FILE, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
def _save(state):
|
||||
with open(STATE_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(state, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# --- prompt 模板 ---
|
||||
CONTINUATION_PROMPT = """[Goal Mode — 持续优化]
|
||||
|
||||
<objective>
|
||||
{objective}
|
||||
</objective>
|
||||
|
||||
⏱ 已用 {elapsed_min:.0f} 分钟,剩余约 {remaining_min:.0f} 分钟。第 {turn} 次唤醒。
|
||||
|
||||
你正在 Goal Mode 下工作:无法宣告完成,你会被无法阻止地持续唤醒直到预算耗尽
|
||||
唤醒后流程(3选1):
|
||||
1. 创造阶段(第一次唤醒):分析objective,在cwd建工作文件夹,严格按照objective执行
|
||||
2. 检验阶段:从不同视角检验创造结果,产出检验报告
|
||||
- 换身份查看(读者/受众/用户/测试工程师/领导) | 设计未跑过的更难测例 | 查素材/事实/引用的真实性与数量/说服力 | 代码质量/产物格式/美观 | 实测验证(亲自执行/模拟用户操作)
|
||||
- 按任务类型**轮换**选用合适的角色和方法
|
||||
- 在遵循原始需求约束下追求超预期,拒绝保守和平庸,必须提出“不够出色”的点
|
||||
- 先保及格线(无事实错误/乱码/格式错误,能运行,过基础测例,遵循用户约束),及格同时追求出色
|
||||
3. 改进阶段:针对检验报告优化改进交付物,必须实质性改进
|
||||
|
||||
原则:
|
||||
1. 每次唤醒**交替**进行检验阶段和改进阶段,保留每次的检验报告和改进changelog。
|
||||
2. 除非发现严重问题,不要对创造结果进行完全重写,而是改进
|
||||
3. 严格区分交付物和进度报告,交付物中不要混入`已检验`等中间信息
|
||||
4. 若检验都是无关紧要问题,下次升级检验(要求更出色产物/更苛刻视角/更难测试/对照原始需求重审/开subagent第三方评审)
|
||||
5. 改进阶段禁止产出"无改动"。若检验未发现值得改的点,说明检验标准太低——本轮产出"检验标准升级报告",论证当前标准为何不够高并提出新标准,下轮按新标准重新检验。
|
||||
6. 在工作文件夹中记录进度,不要更新全局记忆
|
||||
7. 所有阶段都建议进行充分调研:web调研、查看记忆和相关SOP、获取用户倾向
|
||||
8. 禁止进行sha1等无用验证,文件版本不会出错
|
||||
"""
|
||||
|
||||
BUDGET_LIMIT_PROMPT = """[Goal Mode — 预算耗尽,收口]
|
||||
|
||||
<objective>
|
||||
{objective}
|
||||
</objective>
|
||||
|
||||
⏱ 预算已耗尽({budget_min:.0f} 分钟)。这是最后一轮。
|
||||
|
||||
请执行收口:
|
||||
1. 总结本次 goal 的所有进展(列表)
|
||||
2. 列出未完成的事项和建议的 next step
|
||||
3. 确保工作文件夹中记录了关键成果
|
||||
4. 清理一些确定无用的中间临时文件和不再用的进程
|
||||
{done_prompt}
|
||||
"""
|
||||
|
||||
# --- 主逻辑 ---
|
||||
def check():
|
||||
state = _load()
|
||||
if state is None: return '/exit'
|
||||
|
||||
status = state.get('status', 'running')
|
||||
if status != 'running': return '/exit'
|
||||
|
||||
start_time = state.get('start_time', time.time())
|
||||
budget_sec = state.get('budget_seconds', 1800) # 默认30分钟
|
||||
elapsed = time.time() - start_time
|
||||
remaining = budget_sec - elapsed
|
||||
turn = state.get('turns_used', 0) + 1
|
||||
max_turns = state.get('max_turns', 50) # 防空转上限
|
||||
|
||||
# 预算耗尽或轮次上限
|
||||
if remaining <= 0 or turn > max_turns:
|
||||
state['status'] = 'wrapping_up'
|
||||
_save(state)
|
||||
return BUDGET_LIMIT_PROMPT.format(
|
||||
objective=state['objective'],
|
||||
budget_min=budget_sec / 60,
|
||||
done_prompt=state.get('done_prompt', '')
|
||||
)
|
||||
|
||||
# 正常continuation
|
||||
state['turns_used'] = turn
|
||||
_save(state)
|
||||
return CONTINUATION_PROMPT.format(
|
||||
objective=state['objective'],
|
||||
elapsed_min=elapsed / 60,
|
||||
remaining_min=remaining / 60,
|
||||
turn=turn
|
||||
)
|
||||
|
||||
def on_done(result):
|
||||
state = _load()
|
||||
if state is None: return
|
||||
|
||||
if state.get('status') == 'wrapping_up':
|
||||
state['status'] = 'done_budget'
|
||||
state['end_time'] = time.time()
|
||||
_save(state)
|
||||
@@ -0,0 +1,131 @@
|
||||
import os, json, time as _time, socket as _socket, logging
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# 端口锁:防止重复启动,bind失败时agentmain会直接崩溃退出
|
||||
# reload时mod.__dict__保留_lock,跳过重复绑定
|
||||
try: _lock
|
||||
except NameError:
|
||||
_lock = _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM)
|
||||
_lock.bind(('127.0.0.1', 45762)); _lock.listen(1)
|
||||
|
||||
INTERVAL = 120
|
||||
ONCE = False
|
||||
|
||||
_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
TASKS = os.path.join(_dir, '../sche_tasks')
|
||||
DONE = os.path.join(_dir, '../sche_tasks/done')
|
||||
_LOG = os.path.join(_dir, '../sche_tasks/scheduler.log')
|
||||
|
||||
os.makedirs(DONE, exist_ok=True)
|
||||
_logger = logging.getLogger('scheduler')
|
||||
if not _logger.handlers:
|
||||
_logger.setLevel(logging.INFO)
|
||||
_fh = logging.FileHandler(_LOG, encoding='utf-8')
|
||||
_fh.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M'))
|
||||
_logger.addHandler(_fh)
|
||||
|
||||
# 默认最大延迟窗口(小时),超过此时间不触发
|
||||
DEFAULT_MAX_DELAY = 6
|
||||
_l4_t = 0 # last L4 archive time
|
||||
|
||||
def _parse_cooldown(repeat):
|
||||
"""解析repeat为冷却时间(比实际周期略短,防漂移)"""
|
||||
if repeat == 'once': return timedelta(days=999999)
|
||||
if repeat in ('daily', 'weekday'): return timedelta(hours=20)
|
||||
if repeat == 'weekly': return timedelta(days=6)
|
||||
if repeat == 'monthly': return timedelta(days=27)
|
||||
if repeat.startswith('every_'):
|
||||
try:
|
||||
parts = repeat.split('_')
|
||||
n = int(parts[1].rstrip('hdm'))
|
||||
u = parts[1][-1]
|
||||
if u == 'h': return timedelta(hours=n)
|
||||
if u == 'm': return timedelta(minutes=n)
|
||||
if u == 'd': return timedelta(days=n)
|
||||
except (ValueError, IndexError):
|
||||
pass # fall through to warning below
|
||||
_logger.warning(f'Unknown repeat type: {repeat}, fallback to 20h cooldown')
|
||||
return timedelta(hours=20)
|
||||
|
||||
def _last_run(tid, done_files):
|
||||
"""找最近一次执行时间"""
|
||||
latest = None
|
||||
for df in done_files:
|
||||
if not df.endswith(f'_{tid}.md'): continue
|
||||
try:
|
||||
t = datetime.strptime(df[:15], '%Y-%m-%d_%H%M')
|
||||
if latest is None or t > latest: latest = t
|
||||
except: continue
|
||||
return latest
|
||||
|
||||
def check():
|
||||
# L4 archive cron (silent, every 12h)
|
||||
global _l4_t
|
||||
if _time.time() - _l4_t > 43200:
|
||||
_l4_t = _time.time()
|
||||
try:
|
||||
import sys; sys.path.insert(0, os.path.join(_dir, '../memory/L4_raw_sessions'))
|
||||
from compress_session import batch_process
|
||||
raw_dir = os.path.join(_dir, '../temp/model_responses')
|
||||
r = batch_process(raw_dir, dry_run=False)
|
||||
print(f'[L4 cron] {r}')
|
||||
except Exception as e:
|
||||
_logger.error(f'L4 archive failed: {e}')
|
||||
|
||||
if not os.path.isdir(TASKS): return None
|
||||
now = datetime.now()
|
||||
os.makedirs(DONE, exist_ok=True)
|
||||
done_files = set(os.listdir(DONE))
|
||||
for f in sorted(os.listdir(TASKS)):
|
||||
if not f.endswith('.json'): continue
|
||||
tid = f[:-5]
|
||||
try:
|
||||
with open(os.path.join(TASKS, f), encoding='utf-8') as fp:
|
||||
task = json.loads(fp.read())
|
||||
except Exception as e:
|
||||
_logger.error(f'JSON parse error for {f}: {e}')
|
||||
continue
|
||||
if not task.get('enabled', False): continue
|
||||
|
||||
repeat = task.get('repeat', 'daily')
|
||||
sched = task.get('schedule', '00:00')
|
||||
try:
|
||||
h, m = map(int, sched.split(':'))
|
||||
except Exception as e:
|
||||
_logger.error(f'Invalid schedule format in {f}: {sched!r} ({e})')
|
||||
continue
|
||||
|
||||
# weekday任务:周末跳过
|
||||
if repeat == 'weekday' and now.weekday() >= 5: continue
|
||||
|
||||
# 还没到schedule时间就跳过
|
||||
if now.hour < h or (now.hour == h and now.minute < m): continue
|
||||
|
||||
# 执行窗口检查:超过max_delay小时则跳过(防止开机太晚触发过时任务)
|
||||
max_delay = task.get('max_delay_hours', DEFAULT_MAX_DELAY)
|
||||
sched_minutes = h * 60 + m
|
||||
now_minutes = now.hour * 60 + now.minute
|
||||
if (now_minutes - sched_minutes) > max_delay * 60:
|
||||
_logger.info(f'SKIP {tid}: {now_minutes - sched_minutes}min past schedule, '
|
||||
f'exceeds max_delay={max_delay}h')
|
||||
continue
|
||||
|
||||
# 检查冷却
|
||||
last = _last_run(tid, done_files)
|
||||
cooldown = _parse_cooldown(repeat)
|
||||
if last and (now - last) < cooldown: continue
|
||||
|
||||
# 触发
|
||||
_logger.info(f'TRIGGER {tid} (repeat={repeat}, schedule={sched}, '
|
||||
f'last_run={last})')
|
||||
ts = now.strftime('%Y-%m-%d_%H%M')
|
||||
rpt = os.path.join(DONE, f'{ts}_{tid}.md')
|
||||
prompt = task.get('prompt', '')
|
||||
return (f'[定时任务] {tid}\n'
|
||||
f'[报告路径] {rpt}\n\n'
|
||||
f'先读 scheduled_task_sop 了解执行流程,然后执行以下任务:\n\n'
|
||||
f'{prompt}\n\n'
|
||||
f'完成后将执行报告写入 {rpt}。')
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user