Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,17 @@ Overview, how to try it and the known limits: docs/autonomy.md.

### Fixed

- **Memory store** — a symbolic link *above* the configured memory root (macOS `/tmp` and
`/var`, a symlinked home or project folder, `STIRA_CHAT_DATA_DIR` or a `--base-dir` under
them) no longer refuses the store with `MemorySymlinkError`: the ancestors are the
operator's choice and are resolved once when the store is built. A link at the root
itself, at `agents/<id>/memory`, at a layer or `emotion/` directory, at `index.sqlite3`
or at an entry file is still refused, also when swapped in later; the per-read check now
starts at the root, which halves the `lstat` calls of a read (#296).
- **Dependencies** — `strands-agents-tools` is held below 0.8.10 (`>=0.8.0,<0.8.10`): the
exec bridge refuses a release it was not audited against (0.8.9), so a fresh install
got 0.8.10 and no exec-class tools. Deliberate until a newer release is audited (#296).

- **Schedules** — Resume on `/schedules` with the default browser confirmation channel opens the
confirmation (checkbox) dialog after the resume dialog instead of answering "Cancelled; nothing
was applied": the shared dialog lets the pending request's own host confirmation open
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ yet. Known gaps on current `main`:
hidden by default and have no sandbox or background execution yet. Even with
`STIRA_ENABLE_EXEC_TOOLS=1` they are registered only when the installed `strands-agents-tools`
is a release the exec bridge was audited against — today **0.8.9** only. `pyproject.toml`
allows `>=0.8.0`, so a fresh install can get a newer release (0.8.10 at the time of writing);
the log then says `Not registering shell: …`. Check with
`python -m pip show strands-agents-tools`, pin with
`python -m pip install 'strands-agents-tools==0.8.9'`.
therefore holds `strands-agents-tools` below 0.8.10 (`>=0.8.0,<0.8.10`), so a fresh install
gets 0.8.9; this pin is a deliberate safety choice until a newer release is audited. An older
or a manually upgraded release is refused for the exec class (the log says
`Not registering shell: …`). Check with `python -m pip show strands-agents-tools`.
- **Emotion classification is basic.** With the default install the stimulus classifier is the
rule-based one: its patterns are English and it detects almost nothing in Japanese. The
Japanese Sokudan classifier needs the `jev` extra on Python 3.11
Expand Down Expand Up @@ -536,7 +536,7 @@ tests/ # test suite (offline by default)

- Python 3.11+ (3.11 is the baseline on every OS; the `jev` extra needs 3.11)
- strands-agents >= 1.57.1
- strands-agents-tools >= 0.8.0
- strands-agents-tools >= 0.8.0, < 0.8.10 (held at the release the exec bridge was audited against)
- A model provider: Amazon Bedrock (default; AWS credentials) **or** any Strands
provider without AWS — Ollama, llama.cpp, OpenAI, Anthropic, Gemini, … — see
[Model providers](docs/providers.md)
Expand Down
4 changes: 2 additions & 2 deletions README_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ StiraCore は**アルファ版**です。動きますが、未完成の部分が
- **安定性の約束はありません。** バージョンは `0.1.0.dev0` です。Python API、設定キー、イベントやファイルの形式、データディレクトリ(`.local/stira/`)の構成は、移行コードなしにコミット間で変わることがあります([CHANGELOG](CHANGELOG.md))。
- **ローカル・単一ユーザー向けのサーバーです。** Web アプリにログインはありません。ループバックで待ち受け、`STIRA_ALLOW_REMOTE=1` がない限りそれ以外のアドレスを拒否します。1 つのデータディレクトリで動かせるサーバーは 1 つです([セキュリティモデル](#セキュリティモデル)、[運用 §1](docs/operations_ja.md#1-起動停止状態確認))。
- **目標グラント(自律)は実験的で、既定オフです。** Amazon Bedrock のみ、反復内のプロンプトキャッシュなし、費用は見守り付きの 1 回の実行からの推定だけ、グラント内の `run_checks` は macOS のみです([既知の制約](docs/autonomy_ja.md#4-既知の制約))。
- **実行系ツール**(`shell`・`batch`・`cron`・`graph`・`swarm`・`think`・`use_agent`)は既定で非公開で、sandbox もバックグラウンド実行もまだありません。`STIRA_ENABLE_EXEC_TOOLS=1` にしても、導入済みの `strands-agents-tools` が実行系ブリッジの監査済みリリース(現在は **0.8.9** のみ)でなければ登録されません。`pyproject.toml` は `>=0.8.0` を許すので、新しく入れると新しいリリース(執筆時点で 0.8.10)が入ることがあり、そのときログに `Not registering shell: …` と出ます。確認は `python -m pip show strands-agents-tools`、固定は `python -m pip install 'strands-agents-tools==0.8.9'` です。
- **実行系ツール**(`shell`・`batch`・`cron`・`graph`・`swarm`・`think`・`use_agent`)は既定で非公開で、sandbox もバックグラウンド実行もまだありません。`STIRA_ENABLE_EXEC_TOOLS=1` にしても、導入済みの `strands-agents-tools` が実行系ブリッジの監査済みリリース(現在は **0.8.9** のみ)でなければ登録されません。そのため `pyproject.toml` は `strands-agents-tools` を 0.8.10 未満(`>=0.8.0,<0.8.10`)に固定しており、新規インストールでは 0.8.9 が入ります。この固定は、新しいリリースを監査するまでの意図的な安全上の選択です。古いリリースや手動で上げたリリースでは実行系は登録されず、ログに `Not registering shell: …` と出ます。確認は `python -m pip show strands-agents-tools` です。
- **感情の分類は簡素です。** 既定のインストールでは刺激分類器はルールベースで、そのパターンは英語のため、日本語ではほとんど何も検出しません。日本語の Sokudan 分類器には Python 3.11 と `jev` extra が必要です([刺激分類器](#刺激分類器)、[日本語ルール分類器(任意)](docs/ja-rules-classifier_ja.md))。失敗・苛立ち・圧力の刺激カテゴリはまだありません。
- **一方向の長い対話では Neural State が飽和します。** stability は 0 に近づき、ブレーカーはほとんどのターンで CRITICAL(書き込みツールが制限される)になります。これは設計どおりで、調整できる設定は [neural-saturation_ja](docs/neural-saturation_ja.md) にあります。
- **記憶ページ**は、各層の最新 20 件を本文だけ表示する読み取り専用の一覧です。検索・編集はできず、重要度や保持期間も表示されません。手続き記憶の提案はこのページでレビューできますが、意味記憶の staging はコマンドライン(`stira_ctl.sh staging`)だけです。
Expand Down Expand Up @@ -315,7 +315,7 @@ tests/ # テスト(既定はオフライン)

- Python **3.11 以上**(全 OS の基準は 3.11。`jev` extra は 3.11 が必要)
- `strands-agents >= 1.57.1`
- `strands-agents-tools >= 0.8.0`
- `strands-agents-tools >= 0.8.0, < 0.8.10`(実行系ブリッジの監査済みリリースに固定)
- モデルプロバイダ: Amazon Bedrock(既定。AWS 認証が必要)**または** AWS 不要の Strands プロバイダ(Ollama / llama.cpp / OpenAI / Anthropic / Gemini など)。[モデルプロバイダ](docs/providers_ja.md)を参照
- Web アプリには Flask(`dashboard` または `dev` extra)
- Sokudan 分類器を使う場合は **Python 3.11** と `jev` extra
Expand Down
72 changes: 72 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,78 @@ def _yolo_off_unless_asked(monkeypatch):
yield


# --- YOLO clocks off macOS: the runtime gate sees a measured clock (#296) ---------------
# ADR 0012 §7 measured the clocks on macOS only, so the product refuses a YOLO window
# elsewhere (``clock_not_measured``). The window logic itself is platform independent:
# off macOS the tests see the runtime gate (``stiracore.yolo.runtime.clock_support``) as
# measured, and the readings stay the product's own fallback (``host_ops.default_clock``:
# wall + ``time.monotonic``). Nothing changes on macOS. The refusal itself stays covered
# with explicit platforms / ``clocks_supported=False`` (test_yolo_allowance,
# test_yolo_requests); a test that needs the real gate uses ``@pytest.mark.real_yolo_clock``.


@pytest.fixture(autouse=True)
def _yolo_clock_gate_measured_off_macos(request, monkeypatch):
try:
from stiracore.yolo import allowance, runtime
except ImportError: # pragma: no cover
yield
return
real = allowance.clock_support()
if not real.supported and request.node.get_closest_marker("real_yolo_clock") is None:
measured = allowance.ClockSupport(True, None, real.platform)
monkeypatch.setattr(runtime, "clock_support", lambda: measured)
yield


# --- Server apps built by a test are stopped and closed after it (#296) ----------------
# ``create_app`` registers its stops with ``atexit`` (one app per process in production)
# and holds ``server.lock`` until the process exits. A test process builds hundreds of
# apps: each kept its SQLite connections (with -wal/-shm) and its lock descriptor open,
# ~14 descriptors per app, which exhausted the macOS runner's limit of 256 (EMFILE).
# After each test the app ``atexit`` stops it registered (``stiracore`` / ``dashboard``
# functions only) run (idempotent) and are removed,
# and the data-directory locks it took are released. Higher-scoped fixtures are untouched.


@pytest.fixture(autouse=True)
def _close_what_the_test_built(monkeypatch):
import atexit
import gc

from stiracore import owner_lock

registered, locks = [], []
real_register, real_acquire = atexit.register, owner_lock.OwnerLockFile.acquire

def register(function, *args, **kwargs):
# Only the app's own stops: pytest's (tmp_path cleanup) and others stay at exit.
if getattr(function, "__module__", "").startswith(
("stiracore", "dashboard", "stira_server")
):
registered.append(function)
return real_register(function, *args, **kwargs)

def acquire(self, *args, **kwargs):
result = real_acquire(self, *args, **kwargs)
locks.append(self)
return result

monkeypatch.setattr(atexit, "register", register)
monkeypatch.setattr(owner_lock.OwnerLockFile, "acquire", acquire)
yield
for function in reversed(registered):
atexit.unregister(function)
with contextlib.suppress(Exception):
function()
for lock in locks:
if lock.held:
with contextlib.suppress(Exception):
lock.release()
if registered or locks:
gc.collect()


# --- YOLO host dialog: never on the developer's screen (ADR 0012 §8b) ------------------
# A test that builds the server with STIRA_YOLO_ALLOW would otherwise check for, and
# show, the macOS host-confirmation dialog. Every osascript call answers "no" here (no
Expand Down
14 changes: 8 additions & 6 deletions docs/deploy-ec2.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,13 @@ from view, it keeps its lock and databases open on the old file system, and a se
started on the new, empty directory ran at the same time (the data-directory lock no longer
excluded it). An existing installation is moved with the steps in §6.

The checkout and `.local/stira` must be real directories: **a symbolic link anywhere on the
data path stops the server** (measured: with `.local/stira` as a symlink to another
directory the server dies at start-up with `MemorySymlinkError: Memory paths must not
contain symbolic links.` and a traceback). To put the data on its own volume, mount it at
that path ([§6](#6-data-backup-and-restore)).
**A symbolic link at or below the memory directory (`.local/stira/memory`) stops the
server** at start-up with `MemorySymlinkError: Memory paths must not contain symbolic
links.` and a traceback. The directories above it are the operator's choice and are
resolved once at start (#296); before that change a symlinked `.local/stira` stopped the
server too (measured on this guide's instance; the new behaviour was not re-measured on
EC2). To put the data on its own volume, mount it at that path
([§6](#6-data-backup-and-restore)).

## 3. Reaching the server without opening it

Expand Down Expand Up @@ -677,7 +679,7 @@ Run on 2026-10-18 (Ubuntu 24.04 aarch64 VM, systemd 255, Python 3.12.3, the serv
| Flush before the snapshot; migration and `environ` check as a normal administrator | `sync -f` (exit 0 / 1 for a bad path), `fsfreeze -f/-u`, `cd` into `0700` `.local`, `sudo tr < …/environ` vs `sudo cat` | `sync -f` and the absolute-path forms work; the redirect forms are refused |
| Volume mounted over a running server | start, then `mount` | files hidden, old file system still open, a second server ran: mount before the first start; moving existing data (copy + `diff`) works |
| Anchor drop-in without `daemon-reload` | add drop-in to a running unit, `restart` | warning, variable and state directory absent; with `daemon-reload` both present |
| Symlinked data directory | symlink as `.local/stira` | server dies: `MemorySymlinkError` |
| Symlinked data directory (before #296) | symlink as `.local/stira` | server dies: `MemorySymlinkError` (since #296 only a link at or below `.local/stira/memory`) |
| Restore lock | `stira tool-rules lock` / `status` as `stira` | `locked (restored_backup)`, status exit 1 |
| Rule-rollback anchor with `StateDirectory=` | `init-anchor`, then data directory wiped | `LOCKED (anchor_rollback)`; variable set before the anchor exists → `anchor_missing` |
| `smoke_server.py` as `stira` | run | `smoke: PASS in 1.0s` |
Expand Down
11 changes: 6 additions & 5 deletions docs/deploy-ec2_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,11 @@ sudo install -d -o stira -g stira -m 0700 /opt/stira/StiraCore/.local /opt/stira
サーバーは古いファイルシステム上でロックとデータベースを開いたままで、新しい空のディレクトリで起動した 2 台目のサーバーが同時に
動きました(データディレクトリのロックでは排除されなくなる)。既存の環境を移す手順は §6 にあります。

チェックアウトと `.local/stira` は実ディレクトリでなければなりません。**データのパスのどこかにシンボリックリンクがあると
サーバーは止まります**(実測: `.local/stira` を別ディレクトリへのシンボリックリンクにすると、起動時に
`MemorySymlinkError: Memory paths must not contain symbolic links.` とトレースバックを出して終了)。データを専用ボリュームに
置くには、そのパスにマウントします([§6](#6-データバックアップ復元))。
**メモリのディレクトリ(`.local/stira/memory`)またはその下にシンボリックリンクがあるとサーバーは止まります**
(起動時に `MemorySymlinkError: Memory paths must not contain symbolic links.` とトレースバックを出して終了)。
それより上のディレクトリは運用者の選択で、起動時に一度だけ実パスに解決されます(#296)。この変更の前は `.local/stira`
自体がシンボリックリンクでも止まりました(このガイドのインスタンスで実測。新しい挙動は EC2 では再測定していません)。
データを専用ボリュームに置くには、そのパスにマウントします([§6](#6-データバックアップ復元))。

## 3. サーバーを開けずに使う

Expand Down Expand Up @@ -590,7 +591,7 @@ AWS 上のものは**実施していません**。
| スナップショット前のフラッシュ。通常の管理者としての移行と `environ` の確認 | `sync -f`(終了コード 0 / 不正パスで 1)、`fsfreeze -f/-u`、`0700` の `.local` への `cd`、`sudo tr < …/environ` と `sudo cat` | `sync -f` と絶対パスの形は動き、リダイレクトの形は拒否される |
| 稼働中のサーバーの上にボリュームをマウント | 起動してから `mount` | ファイルが見えなくなり、古いファイルシステムが開いたままで 2 台目が動いた: 最初の起動前にマウント。既存データの移動(コピー + `diff`)は動く |
| `daemon-reload` なしのアンカーのドロップイン | 稼働中のユニットにドロップインを足し `restart` | 警告が出て、変数も状態ディレクトリも無い。`daemon-reload` すると両方ある |
| シンボリックリンクのデータディレクトリ | `.local/stira` をシンボリックリンクに | サーバーが停止: `MemorySymlinkError` |
| シンボリックリンクのデータディレクトリ(#296 以前) | `.local/stira` をシンボリックリンクに | サーバーが停止: `MemorySymlinkError`(#296 以降は `.local/stira/memory` 以下のリンクのみ) |
| 復元時のロック | `stira` として `stira tool-rules lock` / `status` | `locked (restored_backup)`、終了コード 1 |
| `StateDirectory=` を使ったルール巻き戻し防止のアンカー | `init-anchor`、その後データディレクトリを消去 | `LOCKED (anchor_rollback)`。アンカー作成前に変数を設定 → `anchor_missing` |
| `stira` としての `smoke_server.py` | 実行 | `smoke: PASS in 1.0s` |
Expand Down
3 changes: 2 additions & 1 deletion docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ Sources: [tool audit](tool-audit.md), current
and upstream `strands-agents-tools` 0.8.9 dependency/source inspection. The audit
is a catalog decision record, **not an OS test matrix**. Entries below describe
expected native-OS support, not successful end-to-end execution on every OS.
Dependency versions can change because installation is not pinned.
`strands-agents-tools` is held below 0.8.10 (`pyproject.toml`, the audited 0.8.9); other
dependency versions are not pinned and can change.

- ✅ = registered candidate with no known OS-specific blocker in this inspection;
still requires successful dependency imports, permissions, and any service setup.
Expand Down
4 changes: 2 additions & 2 deletions docs/install_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,8 @@ py -3.11 -m venv .venv311
[`READ_TOOLS` / `WRITE_TOOLS` registry](../src/stiracore/tools/tool_registry.py)、
および上流 `strands-agents-tools` 0.8.9 の依存・ソース確認です。棚卸しは採否の記録であり、
**OS ごとの動作試験結果ではありません**。以下はネイティブ OS で期待される対応状況で、
全 OS でのエンドツーエンド実行成功を示しません。インストールはバージョン固定ではないため、
依存の更新で変わる場合があります。
全 OS でのエンドツーエンド実行成功を示しません。`strands-agents-tools` は 0.8.10 未満(監査済みの 0.8.9)に
固定していますが、他の依存は固定していないため更新で変わる場合があります。

- ✅ = 登録候補で、今回の確認では OS 固有の障害が見つかっていないもの。
依存の import 成功、権限、サービス設定などは別途必要です。
Expand Down
3 changes: 2 additions & 1 deletion docs/tool-execution-paths.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ roots; they have their own boundary, set by the host:
`memory_tools.memory_write(layer, key, content)` → only `episodic` (other layers:
"denied") → `MemoryStore.write` → `_path`: `key` must be a relative POSIX path without
`.`/`..`/`\`/NUL, under the host-configured `memory_dir/<layer>/`, no symlinked
component, resolved inside `memory_dir` → `_require_writable` (floor: SOUL/IPD and the
component at or below the store root (its ancestors are resolved once when the store is
built, #296), resolved inside `memory_dir` → `_require_writable` (floor: SOUL/IPD and the
store's `read_only_paths`) → symlink re-check → `mkdir(parents)` below
`memory_dir/<layer>` → resolved parent inside `memory_dir` → `NamedTemporaryFile` in that
same parent → `os.replace` onto the checked path. So the temp file, the rename and the
Expand Down
Loading
Loading