Профиль агента: studio (Коди, мандаты без изменений) | client — агент клиента Shkor Labs Autopilot со своим именем, владельцем и инструментами тарифа (SIMBIOT_PROFILE, SIMBIOT_AGENT_DISPLAY_NAME, SIMBIOT_OWNER_NAME, SIMBIOT_COMPANY) + тест
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3d8b743d7a
commit
578856081e
3 changed files with 136 additions and 2 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Контекст ∝ масштабу: чем крупнее цикл, тем больше контекст и модель.
|
||||
"""
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass, field, replace
|
||||
import os
|
||||
|
||||
SEC = 1
|
||||
|
|
@ -15,6 +15,22 @@ QUARTER = 91 * DAY
|
|||
YEAR = 366 * DAY # округляем год как 366 дней для простоты выравнивания
|
||||
|
||||
|
||||
def _env_cfg(key: str, default: str = "") -> str:
|
||||
"""Значение настройки: env сильнее .env харнеса (мини-ридер без circular import)."""
|
||||
val = os.environ.get(key, "").strip()
|
||||
if val:
|
||||
return val
|
||||
try:
|
||||
env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".env")
|
||||
with open(env_path, encoding="utf-8") as f:
|
||||
for line in f:
|
||||
if line.startswith(key + "="):
|
||||
return line.split("=", 1)[1].strip().strip('"').strip("'").strip() or default
|
||||
except OSError:
|
||||
pass
|
||||
return default
|
||||
|
||||
|
||||
def _cfg_model(default: str = "auto") -> str:
|
||||
"""SIMBIOT_MODEL (визард первого запуска, t-261002193628-1d1e5f):
|
||||
одна модель для всех LLM-уровней вместо auto — env сильнее .env
|
||||
|
|
@ -182,6 +198,72 @@ LEVELS = {
|
|||
),
|
||||
}
|
||||
|
||||
# ── Профиль агента (2026-10-18) ──────────────────────────────────────────────
|
||||
# studio — Коди, агент студии/кооператива (мандаты выше, без изменений).
|
||||
# client — агент клиента Shkor Labs Autopilot на его сервере: своё имя, владелец —
|
||||
# клиент, инструменты тарифа (MCP лабы уровня community). Ставит портал при
|
||||
# развёртывании (scripts/panel_tools.sh пишет SIMBIOT_PROFILE и имена в .env).
|
||||
PROFILE = _env_cfg("SIMBIOT_PROFILE", "studio")
|
||||
AGENT_NAME = _env_cfg("SIMBIOT_AGENT_DISPLAY_NAME", "Коди")
|
||||
OWNER = _env_cfg("SIMBIOT_OWNER_NAME", "Alex")
|
||||
COMPANY = _env_cfg("SIMBIOT_COMPANY", "")
|
||||
|
||||
_CLIENT_COMMON = (
|
||||
"Файлы в чат — только идемпотентно: cli.py tg-deliver-file <путь> -c \"подпись\" "
|
||||
"(повтор — SKIP); прямые curl-отправки из shell запрещены. "
|
||||
"Инструмент portal_mcp — MCP лаборатории Shkor Labs на тарифе Autopilot: "
|
||||
"git_repo_create/git_repo_list/git_repo_info — личные репозитории владельца на "
|
||||
"git.cil.by; site_request — поддомен <slug>.cil.by на этот сервер (до 3); "
|
||||
"project_create — проект владельца (доля студии от 10%, удерживается с каждой "
|
||||
"оплаты); product_create/product_list — продукты с платёжной ссылкой (стартуют в "
|
||||
"тестовом режиме, боевые платежи включает студия после ревью — заявка в кабинете "
|
||||
"shkor.com/account); webhook_route_* — вебхуки оплат своего проекта; earnings — "
|
||||
"заработок; skill_hub_search/skill_get — готовые скиллы; feature_request — "
|
||||
"пожелания студии. Реестров кооператива, звонков, почты, казны и голосований у "
|
||||
"тебя НЕТ — не обещай их. Если просят то, чего ты не умеешь, скажи прямо и "
|
||||
"предложи, что можно сделать. "
|
||||
"Не исследуй исходники харнеса и не разбирай падения тиков сам — эскалируй: "
|
||||
"cli.py escalate \"причина\" -c \"контекст\" -s critical|warn|info.")
|
||||
|
||||
|
||||
def _client_mandates() -> dict:
|
||||
who = f"«{COMPANY}»" if COMPANY else "своего владельца"
|
||||
ident = (f"Ты — {AGENT_NAME}, AI-агент компании {who} на платформе Shkor Labs "
|
||||
f"(владелец — {OWNER}). Ты живёшь на собственном сервере владельца и "
|
||||
f"работаешь на его проект. Говори о себе в первом лице как {AGENT_NAME}; "
|
||||
"ты НЕ Коди и не агент кооператива 42. Рядом в панели <slug>.ui.shkor.com "
|
||||
"владельцу доступны OpenCode и Claude Code — для разработки и правок кода "
|
||||
"предлагай их. ")
|
||||
return {
|
||||
"min": (f"Ты — минутный пульс харнеса Simbiot. {ident}"
|
||||
"Все неотвеченные сообщения владельца приходят тебе ОДНИМ батчем (секция "
|
||||
"входящих) — обработай его целиком за тик: один сводный ответ "
|
||||
"(tg-reply <id1>,<id2>,… -c \"текст\") или отдельные реплаи, но ни одно "
|
||||
"сообщение не должно остаться без ответа. Тон: кратко, тепло, на языке "
|
||||
"владельца. СНАЧАЛА ОТВЕТ, ПОТОМ РАБОТА: если работа дольше ~30 с — первым "
|
||||
"делом короткий tg-reply («понял, делаю: …»), итог — cli.py tg-reply <id> "
|
||||
"--followup \"итог\". Мелкие задачи до 5 минут делай сам; большие подавай "
|
||||
"задачей (cli.py task -a — результат сам придёт в чат) и скажи об этом. "
|
||||
"Не создавай задачи из служебных сообщений («тест», «привет»). "
|
||||
+ _CLIENT_COMMON),
|
||||
"hour": (f"Ты — часовой поток харнеса Simbiot. {ident}"
|
||||
"Дайджест часа: что сделано по проекту владельца, статусы задач, батч "
|
||||
"задач S/M/L строго в порядке очереди. Эскалации нижних уровней (секция "
|
||||
"ЭСКАЛАЦИИ) — твоя зона: разберись и закрой cli.py escalation-ack <id>. "
|
||||
"Выбор из 2–9 вариантов — cli.py decide -q \"вопрос\" -o ключ=описание. "
|
||||
+ _CLIENT_COMMON),
|
||||
"day": (f"Ты — дневной цикл харнеса Simbiot. {ident}"
|
||||
"Манифест дня: план по проекту владельца, выбор L-задач, дневной отчёт "
|
||||
"владельцу — что сделано, что дальше, что нужно от него (продукт, "
|
||||
"трекшн, следующий уровень в кабинете shkor.com/account). "
|
||||
+ _CLIENT_COMMON),
|
||||
}
|
||||
|
||||
|
||||
if PROFILE == "client":
|
||||
for _name, _mandate in _client_mandates().items():
|
||||
LEVELS[_name] = replace(LEVELS[_name], mandate=_mandate)
|
||||
|
||||
# v0.2+: week/month/quarter/year — включаются по мере готовности
|
||||
FUTURE_LEVELS = ("week", "month", "quarter", "year")
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from datetime import datetime, timezone, timedelta
|
|||
|
||||
from . import es
|
||||
from .levels import LEVELS
|
||||
from . import levels as _levels
|
||||
from . import paths as _paths
|
||||
from . import runtime
|
||||
from . import commands
|
||||
|
|
@ -613,7 +614,7 @@ def build_context(level_cfg, model: dict, batch=None):
|
|||
own = _own_messages(chat_ids, replied_hist) if depth >= 1 else []
|
||||
if own:
|
||||
parts.append("ТВОИ НЕДАВНИЕ СООБЩЕНИЯ В ЭТОТ ЧАТ (сам отправил: итоги задач, "
|
||||
"уведомления, предложения — Alex может отвечать на них):\n"
|
||||
f"уведомления, предложения — {_levels.OWNER} может отвечать на них):\n"
|
||||
+ "\n".join(own))
|
||||
if replied_hist:
|
||||
replied_hist = sorted(replied_hist, key=lambda x: x.get("ts") or "")[-12:]
|
||||
|
|
|
|||
51
tests/test_profile.py
Normal file
51
tests/test_profile.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
"""Профиль агента: studio (Коди) по умолчанию, client — агент клиента Autopilot со своим именем."""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
CODE = ("import json; from harness.levels import LEVELS, PROFILE, OWNER; "
|
||||
"print(json.dumps({'p': PROFILE, 'o': OWNER, 'm': {k: v.mandate for k, v in LEVELS.items()}}))")
|
||||
|
||||
|
||||
def run(env):
|
||||
e = {k: v for k, v in os.environ.items() if not k.startswith("SIMBIOT_")}
|
||||
e.update(env)
|
||||
out = subprocess.run([sys.executable, "-c", CODE], cwd=ROOT, env=e, capture_output=True, text=True, check=True)
|
||||
import json
|
||||
return json.loads(out.stdout)
|
||||
|
||||
|
||||
def main():
|
||||
ok = bad = 0
|
||||
|
||||
def eq(c, msg):
|
||||
nonlocal ok, bad
|
||||
if c:
|
||||
ok += 1
|
||||
else:
|
||||
bad += 1
|
||||
print("FAIL", msg)
|
||||
|
||||
st = run({"SIMBIOT_PROFILE": "studio"})
|
||||
eq(st["p"] == "studio", "studio profile")
|
||||
eq("Коди" in st["m"]["min"] and "@juno42bot" in st["m"]["min"], "studio min is Cody")
|
||||
eq("booking_slots" in st["m"]["min"], "studio has booking")
|
||||
|
||||
cl = run({"SIMBIOT_PROFILE": "client", "SIMBIOT_AGENT_DISPLAY_NAME": "Acme Autopilot",
|
||||
"SIMBIOT_OWNER_NAME": "Ann Bee", "SIMBIOT_COMPANY": "Acme"})
|
||||
eq(cl["o"] == "Ann Bee", "owner")
|
||||
for lvl in ("min", "hour", "day"):
|
||||
m = cl["m"][lvl]
|
||||
eq("Acme Autopilot" in m and "«Acme»" in m and "Ann Bee" in m, f"{lvl}: identity")
|
||||
eq("@juno42bot" not in m and "Алекс" not in m and "Alex" not in m, f"{lvl}: no Cody/Alex")
|
||||
eq("booking_slots" not in m and "list_members" not in m and "treasury_status" not in m, f"{lvl}: no studio tools")
|
||||
eq("git_repo_create" in m and "product_create" in m, f"{lvl}: client tools")
|
||||
eq(cl["m"]["sec"] == st["m"]["sec"], "sec level shared")
|
||||
|
||||
print(f"{ok} ok, {bad} fail")
|
||||
sys.exit(1 if bad else 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in a new issue