Skip to content
Merged
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
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,20 @@ agent/Executor filesystem boundary:

```bash
tx9 logs media-bot --source executor,codex --since 24h --grep failed
tx9 logs media-bot --level warn --since 24h
tx9 logs media-bot --json | jq .
tx9 logs export media-bot --output media-bot-logs.tar.gz
tx9 logs export media-bot --level warn --output media-bot-logs.tar.gz
```

Events carry a best-effort `level` (debug/info/warn/error) parsed from known
log formats; `--level` keeps that level and above, counting unleveled events
as info. The same threshold filters an export's `events.jsonl` and is recorded
as `manifest.filters.level`. Steady-state noise is kept out of the durable
streams: the reconcile loop logs Executor reachability only when it changes,
and capture collapses consecutive identical lines into one event plus a
repeat-count summary while raw text logs stay byte-faithful. Multi-line Hermes
records (tracebacks, embedded files) are grouped into single events.

Known bearer-token forms are redacted by default. Log exports are created
mode `0600`; they can still contain prompts, tool output, and private work, so
treat them as sensitive. Existing boxes begin collecting the new durable
Expand Down
2 changes: 1 addition & 1 deletion docs/tx9-cli-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Docker overview can be read. `tx9 help` remains Docker-independent.
| `tx9 backup <box>` (aliases `export`, `save`) | Flags: `--path` (default `~/Downloads`), `--password`/env/prompt, `--no-encrypt`. Quiesce → archive agent /data → validate → (encrypt) → verify → `<box>-<timestamp>.tx9`. |
| `tx9 import <file.tx9>` (aliases `load`, `restore`) | Flags: `--name`, `--password`/env/prompt. Validate before creating anything; restore staged; arrive quiesced + gateway-disabled + fresh token; fail on name collision. |
| `tx9 mount <add\|list\|remove> ...` | Persist host-directory bind mounts for an agent and recreate only its disposable container. Targets must be below `/mnt`, outside the portable `/data` volume. `add` supports `--read-only` and `--require-mountpoint`. |
| `tx9 logs <box>` | Query durable agent, Executor, Hermes, Codex, and Claude events. Filters include source, age, text, count, and normalized JSONL. `tx9 logs export <box>` creates a mode-0600 portable log bundle from both isolated volumes. |
| `tx9 logs <box>` | Query durable agent, Executor, Hermes, Codex, and Claude events. Filters include source, age, text, severity (`--level`, this level and above; unleveled events count as info), count, and normalized JSONL. `tx9 logs export <box>` creates a mode-0600 portable log bundle from both isolated volumes. |
| `tx9 resources <box>` | Show actual container CPU/RAM limits and volume usage versus advisory budgets. `resources set` updates limits live and persists them; `resources reset` restores 4 CPU/8 GiB (agent) and 2 CPU/2 GiB (Executor). |
| `tx9 gateway <status\|enable\|disable> <box>` | Inspect or control the container-supervised Hermes gateway. Enable requires `--confirm-single-writer`. |
| `tx9 open <box>` | Print (or open) the authenticated dashboard URL (`?_token=`). |
Expand Down
27 changes: 23 additions & 4 deletions guest/hb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,25 @@ EXECUTOR_HOST="${EXECUTOR_HOST:-127.0.0.1}"
_exec_remote() { [[ "$EXECUTOR_HOST" != 127.0.0.1 ]]; }
_executor_url() { printf 'http://%s:%s/mcp' "$EXECUTOR_HOST" "${EXECUTOR_PORT:-4788}"; }

# Steady-state reporting: the reconcile loop re-checks Executor every cycle,
# so unchanged results must not re-log — they flooded the durable agent
# stream with one identical line per cycle. Non-durable state under /tmp
# resets with the container, so the first check after a restart always logs.
HB_RUNTIME_STATE_DIR="${HB_RUNTIME_STATE_DIR:-/tmp/hermes-box-runtime}"
HB_STEADY_QUIET="${HB_STEADY_QUIET:-0}"
_steady_report() { # <key> <state> <message> [stderr]
local key="$1" state="$2" message="$3" fd="${4:-stdout}" state_file
if [[ "$HB_STEADY_QUIET" != 1 ]]; then
if [[ "$fd" == stderr ]]; then echo "$message" >&2; else echo "$message"; fi
return 0
fi
state_file="$HB_RUNTIME_STATE_DIR/$key.state"
[[ "$(cat "$state_file" 2>/dev/null)" != "$state" ]] || return 0
mkdir -p "$HB_RUNTIME_STATE_DIR" 2>/dev/null || true
printf '%s' "$state" >"$state_file" 2>/dev/null || true
if [[ "$fd" == stderr ]]; then echo "$message" >&2; else echo "$message"; fi
}

init() {
local managed=(
"$AGENT_HOME" "$AGENT_HOME/.claude" "$AGENT_HOME/.codex" "$AGENT_HOME/.hermes"
Expand Down Expand Up @@ -152,8 +171,8 @@ _spawn_without_lock_fd() {
_executor_up() {
init
if _exec_remote; then
_wait_port open || { echo "remote executor unreachable at $EXECUTOR_HOST:${EXECUTOR_PORT:-4788}" >&2; return 1; }
echo "executor daemon: reachable (remote)"
_wait_port open || { _steady_report executor unreachable "remote executor unreachable at $EXECUTOR_HOST:${EXECUTOR_PORT:-4788}" stderr; return 1; }
_steady_report executor reachable "executor daemon: reachable (remote)"
return 0
fi
command -v executor >/dev/null 2>&1 || {
Expand All @@ -163,7 +182,7 @@ _executor_up() {
}
_acquire_daemon_lock "$EXECUTOR_LOCK" || { echo "could not acquire Executor lock" >&2; return 1; }
if _port_open; then
echo "executor daemon: running"
_steady_report executor running "executor daemon: running"
_release_daemon_lock "$EXECUTOR_LOCK"
return 0
fi
Expand Down Expand Up @@ -323,7 +342,7 @@ up() {
reconcile() {
init
[[ ! -e "$QUIESCE_FILE" ]] || return 0
_executor_up || return 1
HB_STEADY_QUIET=1 _executor_up || return 1
_start_gateway
}

Expand Down
Loading