diff --git a/README.md b/README.md index 4c91f1cf..4978a30c 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ CowAgent is lightweight, easy to deploy, and built to extend. Plug in any major ## 🏗️ Architecture -CowAgent Architecture +CowAgent Architecture CowAgent is a complete **Agent Harness**: messages flow in through **Channels**; the **Agent Core** plans and reasons over memory, knowledge, and the available tools and skills; **Models** generate the response, which is sent back through the originating channel. Every layer is decoupled and independently extensible. diff --git a/agent/memory/summarizer.py b/agent/memory/summarizer.py index 9da349d1..066549dc 100644 --- a/agent/memory/summarizer.py +++ b/agent/memory/summarizer.py @@ -462,13 +462,12 @@ class MemoryFlushManager: daily_content=daily_content or "(no recent daily records)", ) from agent.protocol.models import LLMRequest - # Scale max_tokens based on input size to avoid truncating large MEMORY.md - input_chars = len(memory_content) + len(daily_content) - dream_max_tokens = max(2000, min(input_chars, 8000)) + # No output cap: the prompt already keeps MEMORY.md concise (~50 + # items), so a hard max_tokens would only risk truncating a large + # rewrite. Let the model use its default output budget. request = LLMRequest( messages=[{"role": "user", "content": user_msg}], temperature=0.3, - max_tokens=dream_max_tokens, stream=False, system=_dream_system_prompt(), ) diff --git a/cli/cli.py b/cli/cli.py index 99b837ab..b266b372 100644 --- a/cli/cli.py +++ b/cli/cli.py @@ -3,7 +3,7 @@ import click from cli import __version__ from cli.commands.skill import skill -from cli.commands.process import start, stop, restart, update, status, logs +from cli.commands.process import start, stop, restart, self_restart, update, status, logs from cli.commands.context import context from cli.commands.install import install_browser from cli.commands.knowledge import knowledge @@ -68,6 +68,7 @@ main.add_command(skill) main.add_command(start) main.add_command(stop) main.add_command(restart) +main.add_command(self_restart) main.add_command(update) main.add_command(status) main.add_command(logs) diff --git a/cli/commands/process.py b/cli/commands/process.py index d6b1aaf4..c7f98448 100644 --- a/cli/commands/process.py +++ b/cli/commands/process.py @@ -195,6 +195,120 @@ def restart(ctx, no_logs): ctx.invoke(start, no_logs=no_logs) +# Detached relay that survives the caller's process tree. Run via `python -c` +# with start_new_session=True so it keeps going after the agent's bash child +# (and the main app it kills) both die. Flow: self-check the new code FIRST +# (import app); abort without touching the old process if it fails. Only when +# the new code is loadable does it SIGTERM the old PID, wait for exit (SIGKILL +# fallback), then start a fresh app.py and write the pid. +_RELAY_SCRIPT = r""" +import os, sys, time, signal, subprocess + +root, python, app_py, pid_file, log_file = sys.argv[1:6] +old_pid = int(sys.argv[6]) if len(sys.argv) > 6 and sys.argv[6] else 0 + + +def alive(pid): + if not pid: + return False + try: + os.kill(pid, 0) + return True + except OSError: + return False + + +def log(msg): + try: + with open(log_file, "a") as f: + f.write("[self-restart] " + msg + "\n") + except OSError: + pass + + +# 0) Self-check: make sure the new code actually loads BEFORE killing anything. +# `import app` exercises top-level imports / syntax of the entry module. If it +# fails, abort and leave the running service untouched — never end up with the +# old process killed and the new one unable to start. +check = subprocess.run( + [python, "-c", "import app"], cwd=root, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, +) +if check.returncode != 0: + detail = (check.stdout or b"").decode("utf-8", "replace").strip() + log("self-check FAILED, aborting restart; service left running:\n" + detail) + sys.exit(1) +log("self-check passed") + +# 1) Ask the old process to exit gracefully (its SIGTERM handler persists state). +if alive(old_pid): + try: + os.kill(old_pid, signal.SIGTERM) + except OSError: + pass + # 2) Wait up to ~15s for it to go, then force-kill as a backstop. + for _ in range(150): + if not alive(old_pid): + break + time.sleep(0.1) + else: + try: + os.kill(old_pid, signal.SIGKILL) + except OSError: + pass + time.sleep(0.5) + +# 3) Start a fresh instance, detached, logging to the same file. +with open(log_file, "a") as f: + proc = subprocess.Popen( + [python, app_py], cwd=root, + stdout=f, stderr=f, start_new_session=True, + ) +with open(pid_file, "w") as f: + f.write(str(proc.pid)) +log("restarted, new pid=" + str(proc.pid)) +""" + + +@click.command(name="self-restart", hidden=True) +def self_restart(): + """Restart from inside the running agent (detached; survives parent death). + + Intended to be invoked by the agent itself (e.g. via bash after editing its + own code), not by users — so it is hidden from `cow help`. Unlike `restart`, + the actual stop+start runs in a detached relay process that outlives the + agent's bash child, which would otherwise die together with the main app it + kills. + """ + if _IS_WIN: + click.echo("self-restart is not supported on Windows; use `cow restart`.", err=True) + sys.exit(1) + + root = get_project_root() + app_py = os.path.join(root, "app.py") + if not os.path.exists(app_py): + click.echo("Error: app.py not found in project root.", err=True) + sys.exit(1) + + python = sys.executable + pid = _read_pid() or 0 + + subprocess.Popen( + [ + python, "-c", _RELAY_SCRIPT, + root, python, app_py, _get_pid_file(), _get_log_file(), str(pid), + ], + cwd=root, + start_new_session=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + click.echo(click.style( + "✓ Restart scheduled. The service will stop and come back in a few seconds.", + fg="green", + )) + + @click.command() @click.pass_context def update(ctx): diff --git a/docs/intro/architecture.mdx b/docs/intro/architecture.mdx index a9797bf1..77fdbc8a 100644 --- a/docs/intro/architecture.mdx +++ b/docs/intro/architecture.mdx @@ -9,7 +9,7 @@ CowAgent 2.0 has evolved from a simple chatbot into a super intelligent assistan CowAgent's architecture consists of the following core modules: -CowAgent Architecture +CowAgent Architecture | Module | Description | | --- | --- | diff --git a/docs/ja/README.md b/docs/ja/README.md index afe51342..1ae5cb84 100644 --- a/docs/ja/README.md +++ b/docs/ja/README.md @@ -48,7 +48,7 @@ CowAgent は軽量でデプロイしやすく、拡張性に優れています ## 🏗️ アーキテクチャ -CowAgent Architecture +CowAgent Architecture CowAgent は完全な **Agent Harness** です:メッセージは各種**チャネル**から流入し、**Agent Core** が記憶・知識・利用可能なツール/Skill を組み合わせてタスクを計画・判断、**モデル**が応答を生成し、結果は元のチャネルに返されます。各レイヤーは疎結合で、独立して拡張可能です。 diff --git a/docs/ja/intro/architecture.mdx b/docs/ja/intro/architecture.mdx index 5e8b202b..682b407e 100644 --- a/docs/ja/intro/architecture.mdx +++ b/docs/ja/intro/architecture.mdx @@ -9,7 +9,7 @@ CowAgent 2.0 は、シンプルなチャットボットから、自律的な思 CowAgent のアーキテクチャは以下のコアモジュールで構成されています: -CowAgent Architecture +CowAgent Architecture | モジュール | 説明 | | --- | --- |