agent-bios 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/DEPENDENCIES.md +236 -80
  2. package/INSTALL.md +112 -0
  3. package/README.md +184 -524
  4. package/claude/CLAUDE.md +1 -1
  5. package/claude/guides/cli-multi-model-workflow.md +1 -1
  6. package/claude/guides/learning-flow.md +23 -12
  7. package/claude/guides/session-distill-workflow.md +22 -12
  8. package/codex/AGENTS.md +1 -1
  9. package/codex/guides/cli-multi-model-workflow.md +1 -1
  10. package/codex/guides/learning-flow.md +23 -12
  11. package/codex/guides/session-distill-workflow.md +22 -12
  12. package/compose/app_bridge/SKILL.md +75 -0
  13. package/compose/app_bridge/agents/openai.yaml +2 -0
  14. package/compose/app_bridge/scripts/bridge.py +76 -0
  15. package/compose/bootstrap/SKILL.md +12 -1
  16. package/compose/corpus.py +31 -9
  17. package/compose/corpus_app.py +456 -0
  18. package/compose/corpus_import.py +529 -0
  19. package/compose/corpus_install.py +196 -18
  20. package/compose/corpus_session.py +27 -0
  21. package/compose/corpus_setup.py +674 -0
  22. package/compose/corpus_setup_cli.py +582 -0
  23. package/compose/corpus_setup_i18n.py +318 -0
  24. package/compose/corpus_setup_ui.py +633 -0
  25. package/compose/corpus_store.py +167 -29
  26. package/compose/corpus_transaction.py +43 -10
  27. package/compose/corpus_ui_runtime.py +278 -0
  28. package/compose/setup/START.md +147 -0
  29. package/compose/ui_runtime/linkify_it_py-2.2.0-py3-none-any.whl +0 -0
  30. package/compose/ui_runtime/manifest.json +238 -0
  31. package/compose/ui_runtime/markdown_it_py-4.2.0-py3-none-any.whl +0 -0
  32. package/compose/ui_runtime/mdit_py_plugins-0.6.1-py3-none-any.whl +0 -0
  33. package/compose/ui_runtime/mdurl-0.1.2-py3-none-any.whl +0 -0
  34. package/compose/ui_runtime/platformdirs-4.11.8-py3-none-any.whl +0 -0
  35. package/compose/ui_runtime/pygments-2.21.0-py3-none-any.whl +0 -0
  36. package/compose/ui_runtime/rich-15.0.0-py3-none-any.whl +0 -0
  37. package/compose/ui_runtime/textual-8.2.8-py3-none-any.whl +0 -0
  38. package/compose/ui_runtime/typing_extensions-4.16.0-py3-none-any.whl +0 -0
  39. package/docs/advanced-launch.md +131 -0
  40. package/docs/assets/corpus-studio.svg +227 -0
  41. package/docs/corpus.md +117 -0
  42. package/docs/recovery.md +201 -0
  43. package/docs/session-model.md +120 -0
  44. package/docs/setup.md +190 -0
  45. package/docs/understand.md +40 -0
  46. package/install.sh +75 -46
  47. package/launch/agent-launch.py +91 -47
  48. package/launch/provision-venv.sh +44 -13
  49. package/learn/collect-learning.py +14 -5
  50. package/learn/learning.schema.json +2 -2
  51. package/package.json +14 -2
  52. package/provenance.json +1 -1
  53. package/wrappers/claude-run.sh +10 -13
package/install.sh CHANGED
@@ -1,32 +1,19 @@
1
1
  #!/usr/bin/env bash
2
2
  # agent-bios installer.
3
3
  #
4
- # Deploys the single-source-of-truth (globals, scoped guides, Codex agent
5
- # templates, Codex wrappers, the launch profile/shell/bin, a managed Textual
6
- # venv, and the zsh hook) into $HOME by COPY — idempotent, backed up before
7
- # overwrite, and reversible. Distributed as an npm bin; the actual $HOME
8
- # deployment is this explicit command (never a postinstall side effect).
4
+ # Stores the corpus and runtime privately for explicitly activated sessions.
5
+ # Native global instructions and host discovery paths remain user-owned.
6
+ # AGENT_BIOS_LEGACY_INSTALL=1 selects the compatibility deployer.
9
7
  #
10
- # Usage:
11
- # agent-bios install deploy into this environment (backs up + verifies)
12
- # agent-bios verify check the deployed state matches the source
13
- # agent-bios status show what is installed and where
14
- # agent-bios cost the session cost / context meter, from any directory
15
- # agent-bios update git pull + reinstall (clone), or print the npm update line
16
- # agent-bios update --check cache the registry's latest version for the TUI badge
17
- # agent-bios uninstall remove deployed files and the zsh hook
18
- # agent-bios help
19
- #
20
- # Flags: --dry-run (print actions, change nothing).
21
- # Env overrides: CLAUDE_CONFIG_DIR, CODEX_HOME, AGENT_LAUNCH_VENV, ZDOTDIR.
8
+ # Usage: agent-bios help
9
+ # Private roots: AGENT_BIOS_STATE_DIR, AGENT_BIOS_CORPUS_DIR.
10
+ # Host/runtime overrides: CLAUDE_CONFIG_DIR, CODEX_HOME, AGENT_LAUNCH_VENV, ZDOTDIR.
22
11
  set -euo pipefail
23
12
 
24
- # This installer is non-interactive: every input arrives as a subcommand, flag,
25
- # or env var. Detach stdin so no child (the codex-helm dry-run, pip, git) can
26
- # block forever on an inherited idle stdin that is what hangs an install under
27
- # CI, pipes, and background runs, where stdin stays open but never delivers.
28
- # `learn` is the one subcommand whose payload IS stdin, so keep the caller's on
29
- # fd 3 first and hand it back only there; every other path still sees /dev/null.
13
+ # Preserve caller input for explicit interactive setup and payload commands;
14
+ # dependency and compatibility child processes receive /dev/null by default.
15
+ # Preserve caller input on fd 3 for payload-bearing subcommands (learn, corpus,
16
+ # understand, and cost); other child processes see /dev/null.
30
17
  # The braces matter. `exec` with redirections and no command applies them to the
31
18
  # SHELL, permanently — so the bare `exec 3<&0 2>/dev/null` this used to be sent
32
19
  # every later error message on this script's stderr to /dev/null: bash's own
@@ -1745,7 +1732,7 @@ cmd_update() {
1745
1732
  if [ "${AGENT_BIOS_LEGACY_INSTALL:-0}" = 1 ]; then
1746
1733
  cmd_install
1747
1734
  else
1748
- python3 "$REPO/compose/corpus_install.py" --repo "$REPO" install
1735
+ python3 "$REPO/compose/corpus_install.py" --repo "$REPO" install --non-interactive
1749
1736
  fi
1750
1737
  else
1751
1738
  log "Installed as an npm package. Update with:"
@@ -1760,9 +1747,22 @@ usage() {
1760
1747
  cat <<'EOF'
1761
1748
  agent-bios — manage private corpus content for explicitly activated sessions.
1762
1749
 
1763
- agent-bios install store a private runtime and corpus baseline
1750
+ agent-bios install open the Textual installation wizard
1751
+ agent-bios install --non-interactive --corpus none store runtime with no active corpus
1752
+ agent-bios install --corpus selected --select @scope/package select only that corpus
1764
1753
  agent-bios onboard select domains for future activated sessions
1754
+ agent-bios setup start report setup languages, execution target, and conversation guide
1755
+ agent-bios setup inspect --language ko inspect dependencies and corpus choices as JSON
1756
+ agent-bios setup discover --project-root /path/to/project list eligible instruction sources
1757
+ agent-bios setup plan --language ko --input request.json preview selected setup effects as JSON
1758
+ agent-bios setup apply --input review.json --review-id ID --yes apply the accepted review
1759
+ agent-bios setup status --review-id ID inspect a recorded setup outcome
1760
+ agent-bios setup resume --review-id ID re-probe and prepare a fresh review without applying
1765
1761
  agent-bios corpus open Corpus Studio; list/show/plan/apply also work non-TTY
1762
+ agent-bios import discover/capture/review local instructions; --help lists source-preserving steps
1763
+ agent-bios app register an explicit Codex app bridge or manage per-task corpus input
1764
+ agent-bios app session use return selected corpus as context for this Codex app task
1765
+ agent-bios app session off stop future delivery; earlier context requires a new task to exclude
1766
1766
  agent-bios understand list corpus learning bundles; --help shows session/discovery commands
1767
1767
  agent-bios shell show the optional zsh connection status
1768
1768
  agent-bios shell restore make bare claude/codex open the launcher TUI
@@ -1795,18 +1795,21 @@ AGENTS.md/CLAUDE.md. Restore/remove it from the TUI's Shell connection menu or
1795
1795
  the commands above. After restoring, open a new terminal or reload .zshrc.
1796
1796
 
1797
1797
  Flags: --dry-run print actions without changing anything
1798
- --domains a,b assemble ONLY the named domain packages (plus core+infra);
1799
- with onboard, 'none' means core+infra only. The selection
1800
- persists in the state dir and later installs/updates reuse
1801
- it. Default (no flag, no saved selection) selects every
1802
- domain one install shape, "full" is just everything
1803
- selected.
1804
- --with a,b also install the named optional dependencies (install only).
1805
- Without it, install offers each missing one when the terminal
1806
- is interactive, and otherwise just prints its install line.
1807
- Missing ones are not fatal — they only degrade the review
1808
- routes that need them.
1809
- Env: CLAUDE_CONFIG_DIR, CODEX_HOME, AGENT_LAUNCH_VENV, ZDOTDIR
1798
+ --interactive open the Textual installation wizard (default)
1799
+ --non-interactive opt out of the wizard and return JSON; required without a terminal
1800
+ --corpus / --select seed wizard choices; --dry-run previews without Apply
1801
+ --corpus none deliver no corpus, including core and management bootstrap
1802
+ --corpus all select all corpus; --corpus selected uses repeated --select targets
1803
+ Library assets remain in the private runtime; these choices govern delivery.
1804
+ --domains a,b select named domains for future activated snapshots, plus
1805
+ implicit core+infra; '--domains none' keeps that core-only meaning. Saved selections
1806
+ survive installs/updates. Without a saved selection or flag,
1807
+ installation selects every domain.
1808
+ --with a,b optional dependency installation in explicit legacy mode only;
1809
+ private installation rejects this option.
1810
+ Env: AGENT_BIOS_STATE_DIR, AGENT_BIOS_CORPUS_DIR (private runtime and user roots)
1811
+ CLAUDE_CONFIG_DIR, CODEX_HOME (native host configuration)
1812
+ AGENT_LAUNCH_VENV, ZDOTDIR
1810
1813
  EOF
1811
1814
  # DERIVED, not typed: the hardcoded pair went stale the moment a capability was renamed,
1812
1815
  # and the name it still advertised selected a different capability than the one that
@@ -1814,11 +1817,10 @@ EOF
1814
1817
  local known; known=$(capability_table 2>/dev/null | cut -f1 | tr '\n' ' ')
1815
1818
  [ -n "$known" ] && printf ' --with names: %s\n' "${known% }"
1816
1819
 
1817
- # Recovery exists in both modes at different granularity, and naming the wrong one is worse
1818
- # than naming none: a clone can roll the corpus back to a registered mining window, while an
1819
- # npm install has no git history to read and rolls the whole package back by version instead.
1820
- # Derived from which install this is, for the same reason --with names is.
1821
- if [ -e "$REPO/.git" ]; then
1820
+ if [ "${AGENT_BIOS_LEGACY_INSTALL:-0}" != 1 ]; then
1821
+ printf '\nRecover: agent-bios corpus history --json; submit an operation=rollback request\n'
1822
+ printf ' through agent-bios corpus plan/apply for a baseline_ref or history_id.\n'
1823
+ elif [ -e "$REPO/.git" ]; then
1822
1824
  printf '\nRecover: python3 %s/compose/corpus-state.py list, then rollback --version V\n' "$REPO"
1823
1825
  else
1824
1826
  printf '\nRecover: npm install -g agent-bios@<older-version> && agent-bios install\n'
@@ -1829,9 +1831,9 @@ EOF
1829
1831
  CMD="${1:-help}"
1830
1832
  if [ $# -gt 0 ]; then shift; fi
1831
1833
 
1832
- # Private installation is the new user path. The explicit legacy flag exists only
1833
- # while maintained install fixtures and pre-migration environments exercise the old
1834
- # writer. No private operation falls through to a global writer.
1834
+ # Private installation is the default path. The explicit legacy flag selects
1835
+ # compatibility installation and its regression fixtures. No private operation
1836
+ # falls through to a global writer.
1835
1837
  if [ "$CMD" = "corpus" ]; then
1836
1838
  exec python3 "$REPO/compose/corpus.py" --repo "$REPO" "$@" <&3
1837
1839
  fi
@@ -1841,9 +1843,28 @@ fi
1841
1843
  if [ "$CMD" = "understand" ]; then
1842
1844
  exec python3 "$REPO/compose/corpus_understand.py" --repo "$REPO" "$@" <&3
1843
1845
  fi
1846
+ if [ "$CMD" = "app" ]; then
1847
+ exec python3 "$REPO/compose/corpus_app.py" --repo "$REPO" "$@" <&3
1848
+ fi
1849
+ if [ "$CMD" = "import" ]; then
1850
+ exec python3 "$REPO/compose/corpus_import.py" --repo "$REPO" "$@" <&3
1851
+ fi
1852
+ if [ "$CMD" = "setup" ]; then
1853
+ if ! command -v python3 >/dev/null 2>&1 || ! python3 -c 'import sys; sys.exit(sys.version_info < (3, 11))' </dev/null; then
1854
+ log "Python 3.11 or newer is required for conversation setup."
1855
+ log "Install Python with your operating system package manager, then rerun this command."
1856
+ exit 1
1857
+ fi
1858
+ exec python3 "$REPO/compose/corpus_setup_cli.py" --repo "$REPO" "$@" <&3
1859
+ fi
1844
1860
  if [ "${AGENT_BIOS_LEGACY_INSTALL:-0}" != 1 ]; then
1845
1861
  case "$CMD" in
1846
1862
  install|onboard|verify|status|uninstall|migrate|reset)
1863
+ if ! command -v python3 >/dev/null 2>&1 || ! python3 -c 'import sys; sys.exit(sys.version_info < (3, 11))' </dev/null; then
1864
+ log "Python 3.11 or newer is required for the installer and dependency chooser."
1865
+ log "Install Python with your operating system package manager, then rerun this command."
1866
+ exit 1
1867
+ fi
1847
1868
  exec python3 "$REPO/compose/corpus_install.py" --repo "$REPO" "$CMD" "$@" <&3
1848
1869
  ;;
1849
1870
  esac
@@ -1857,7 +1878,15 @@ fi
1857
1878
  if [ "$CMD" = "learn" ]; then
1858
1879
  collector="$REPO/learn/collect-learning.py"
1859
1880
  [ -f "$collector" ] || { log "learn: collector missing at $collector"; exit 1; }
1860
- exec python3 "$collector" "$@" <&3
1881
+ learning_python="python3"
1882
+ if ! python3 -c 'from jsonschema import Draft202012Validator' </dev/null >/dev/null 2>&1; then
1883
+ learning_python="${AGENT_LAUNCH_VENV:-$HOME/.local/share/agent-launch/venv}/bin/python"
1884
+ if [ ! -x "$learning_python" ] || ! "$learning_python" -c 'from jsonschema import Draft202012Validator' </dev/null >/dev/null 2>&1; then
1885
+ log "learn: JSON Schema support is missing; select jsonschema in agent-bios install --interactive."
1886
+ exit 1
1887
+ fi
1888
+ fi
1889
+ exec "$learning_python" "$collector" "$@" <&3
1861
1890
  fi
1862
1891
  # `cost` is the same shape for the same reason: the guides tell an installed user to
1863
1892
  # measure with session-cost.py, and on a packaged install that file lives inside the
@@ -1421,6 +1421,18 @@ def parse_capability_offers(name: str, raw: Any) -> tuple:
1421
1421
  return tuple(parsed)
1422
1422
 
1423
1423
 
1424
+ def review_wrapper_command(host: str, name: str) -> str | None:
1425
+ """Resolve a review adapter from the active private package or native installation."""
1426
+ if private_corpus_enabled():
1427
+ path = corpus_package_root() / "wrappers" / f"{name}.sh"
1428
+ elif host == "codex":
1429
+ path = expand_config_path(f"${{CODEX_HOME}}/bin/{name}")
1430
+ else:
1431
+ home = os.environ.get("CLAUDE_CONFIG_DIR") or str(pathlib.Path.home() / ".claude")
1432
+ path = pathlib.Path(home) / "bin" / name
1433
+ return str(path.absolute()) if path.is_file() and os.access(path, os.X_OK) else None
1434
+
1435
+
1424
1436
  def host_dispatch_command(host: str, config: dict[str, Any]) -> str | None:
1425
1437
  """The absolute command that spawns a fresh, exactly-pinned, read-only session on
1426
1438
  `host`, or None when this machine cannot.
@@ -1430,23 +1442,12 @@ def host_dispatch_command(host: str, config: dict[str, Any]) -> str | None:
1430
1442
  named the reviewer's seat but never said how to reach it — the launched agent was
1431
1443
  told to run isolated passes on a model with no way to spawn them."""
1432
1444
  if host == "codex":
1433
- path = expand_config_path("${CODEX_HOME}/bin/codex-run")
1434
- return str(path.absolute()) if path.is_file() and os.access(path, os.X_OK) else None
1445
+ return review_wrapper_command("codex", "codex-run")
1435
1446
  if host == "claude":
1436
- # The twin of the branch above, and the asymmetry it removes was silent: the
1437
- # codex host dispatched through our adapter while the claude host dispatched the
1438
- # bare binary, so half of every cross-family review had nowhere to report from.
1439
- #
1440
- # Falls THROUGH rather than returning None when the adapter is absent, which is
1441
- # the difference from codex: there, codex-run is the only route that can pin a
1442
- # seat, so its absence really is no route. Here the bare binary is a working
1443
- # dispatch that simply cannot produce a receipt, and taking the panel away from
1444
- # a machine that has not deployed the adapter yet would be a worse trade than
1445
- # the missing evidence. An un-updated install keeps exactly today's behaviour.
1446
- home = os.environ.get("CLAUDE_CONFIG_DIR") or str(pathlib.Path.home() / ".claude")
1447
- adapter = pathlib.Path(home) / "bin" / "claude-run"
1448
- if adapter.is_file() and os.access(adapter, os.X_OK):
1449
- return str(adapter.absolute())
1447
+ # The bare Claude backend remains a usable fallback without adapter receipts.
1448
+ adapter = review_wrapper_command("claude", "claude-run")
1449
+ if adapter is not None:
1450
+ return adapter
1450
1451
  backend = config.get("backends", {}).get(host, {})
1451
1452
  try:
1452
1453
  return resolve_command(backend.get("command", ""))
@@ -4116,7 +4117,12 @@ def expand_config_path(value: str) -> pathlib.Path:
4116
4117
  return pathlib.Path(os.path.expandvars(os.path.expanduser(expanded)))
4117
4118
 
4118
4119
 
4120
+ _UI_RUNTIME_RELEASE: Callable[[], None] | None = None
4121
+
4122
+
4119
4123
  def exec_backend(command: str, args: list[str], env: dict[str, str] | None = None) -> NoReturn:
4124
+ if _UI_RUNTIME_RELEASE is not None:
4125
+ _UI_RUNTIME_RELEASE()
4120
4126
  os.execve(command, [command, *args], os.environ.copy() if env is None else env)
4121
4127
 
4122
4128
 
@@ -4564,11 +4570,9 @@ def _resolves(value: str) -> bool:
4564
4570
 
4565
4571
  def cross_native_command(plan: dict[str, Any]) -> str | None:
4566
4572
  """Absolute command a cross-family main dispatches to for native review, or
4567
- None if unresolvable. The codex reviewer wrapper lives off PATH under
4568
- CODEX_HOME/bin; the claude reviewer is the claude backend on PATH."""
4573
+ None if unresolvable. Codex uses the active review adapter; Claude uses its backend."""
4569
4574
  if plan["review_host"] == "codex":
4570
- path = expand_config_path("${CODEX_HOME}/bin/codex-run")
4571
- return str(path.absolute()) if path.is_file() and os.access(path, os.X_OK) else None
4575
+ return review_wrapper_command("codex", "codex-run")
4572
4576
  try:
4573
4577
  return resolve_command(plan["review_backend"])
4574
4578
  except LaunchError:
@@ -4576,11 +4580,10 @@ def cross_native_command(plan: dict[str, Any]) -> str | None:
4576
4580
 
4577
4581
 
4578
4582
  def cross_helm_command(plan: dict[str, Any]) -> str | None:
4579
- """The codex fan-out reviewer wrapper (hybrid), off PATH under CODEX_HOME/bin."""
4583
+ """The Codex fan-out reviewer adapter from the active installation."""
4580
4584
  if plan["review_host"] != "codex":
4581
4585
  return None
4582
- path = expand_config_path("${CODEX_HOME}/bin/codex-helm")
4583
- return str(path) if path.is_file() and os.access(path, os.X_OK) else None
4586
+ return review_wrapper_command("codex", "codex-helm")
4584
4587
 
4585
4588
 
4586
4589
  def cross_ultracode_command(plan: dict[str, Any]) -> str | None:
@@ -4857,11 +4860,54 @@ def setup_summary_lines(plan: dict[str, Any] | None) -> list[str]:
4857
4860
  return lines
4858
4861
 
4859
4862
 
4863
+ def _activate_cli_ui_runtime() -> bool:
4864
+ """Use the package's offline UI bundle; standalone compatibility copies keep their runtime."""
4865
+ global _UI_RUNTIME_RELEASE
4866
+ source = pathlib.Path(__file__).resolve().parents[1]
4867
+ if os.environ.get("AGENT_BIOS_PACKAGE_ROOT") or private_corpus_enabled():
4868
+ root = corpus_package_root()
4869
+ elif (source / "compose/corpus_store.py").is_file():
4870
+ root = source
4871
+ else:
4872
+ return False
4873
+ loader = root / "compose/corpus_ui_runtime.py"
4874
+ if loader.is_symlink() or not loader.is_file():
4875
+ raise LaunchError("bundled UI runtime loader is missing or unsafe; reinstall the agent-bios package")
4876
+ module_root = str(root / "compose")
4877
+ if module_root not in sys.path:
4878
+ sys.path.insert(0, module_root)
4879
+ try:
4880
+ from corpus_ui_runtime import activate_ui_runtime, release_ui_runtime
4881
+ activate_ui_runtime(root)
4882
+ except (ImportError, OSError, RuntimeError) as exc:
4883
+ raise LaunchError(f"bundled terminal UI could not start: {exc}") from exc
4884
+ _UI_RUNTIME_RELEASE = release_ui_runtime
4885
+ return True
4886
+
4887
+
4888
+ def _textual_api() -> tuple[Any, ...]:
4889
+ """Import the public UI APIs used by this launcher without starting an app."""
4890
+ from textual import work
4891
+ from textual.app import App
4892
+ from textual.binding import Binding
4893
+ from textual.containers import Horizontal, Vertical, VerticalScroll
4894
+ from textual.screen import ModalScreen
4895
+ from textual.widgets import Input, OptionList, Static
4896
+ from textual.widgets.option_list import Option
4897
+ from rich.text import Text
4898
+ from textual.theme import Theme
4899
+ return (work, App, Binding, Horizontal, Vertical, VerticalScroll, ModalScreen,
4900
+ Input, OptionList, Static, Option, Text, Theme)
4901
+
4902
+
4860
4903
  def textual_importable() -> bool:
4861
4904
  try:
4862
- import textual # noqa: F401
4863
- except Exception:
4864
- return False
4905
+ _textual_api()
4906
+ except ImportError as exc:
4907
+ if any(exc.name == name or (exc.name or "").startswith(name + ".")
4908
+ for name in ("textual", "rich")):
4909
+ return False
4910
+ raise
4865
4911
  return True
4866
4912
 
4867
4913
 
@@ -4926,15 +4972,8 @@ _UI_CANCEL = "\x00cancel"
4926
4972
  def _build_app_class():
4927
4973
  """Import textual lazily and build the App/Screen classes, so importing this
4928
4974
  module and every non-interactive path stays free of the textual dependency."""
4929
- from textual import work
4930
- from textual.app import App
4931
- from textual.binding import Binding
4932
- from textual.containers import Horizontal, Vertical, VerticalScroll
4933
- from textual.screen import ModalScreen
4934
- from textual.widgets import Input, OptionList, Static
4935
- from textual.widgets.option_list import Option
4936
- from rich.text import Text
4937
- from textual.theme import Theme
4975
+ (work, App, Binding, Horizontal, Vertical, VerticalScroll, ModalScreen,
4976
+ Input, OptionList, Static, Option, Text, Theme) = _textual_api()
4938
4977
 
4939
4978
  # Host-matched palettes so the preflight reads as the CLI it launches.
4940
4979
  # Both are taken from the host's own artifact: Codex from its ~/.codex dark
@@ -8913,9 +8952,10 @@ def run_corpus_apply(selection: list[str]) -> None:
8913
8952
  this function only streams it and reports the exit."""
8914
8953
  status = load_corpus_status() or {}
8915
8954
  domains = ",".join(selection) if selection else "none"
8955
+ interaction = [] if os.environ.get("AGENT_BIOS_LEGACY_INSTALL") == "1" else ["--non-interactive"]
8916
8956
  front = shutil.which("agent-bios")
8917
8957
  if front:
8918
- argv = [front, "onboard", "--domains", domains]
8958
+ argv = [front, "onboard", *interaction, "--domains", domains]
8919
8959
  else:
8920
8960
  # isinstance, not truthiness: `repo` is whatever the JSON holds, and a non-string
8921
8961
  # truthy value reached pathlib.Path() and raised TypeError out of the apply path.
@@ -8928,11 +8968,11 @@ def run_corpus_apply(selection: list[str]) -> None:
8928
8968
  if installer is None or not installer.is_file():
8929
8969
  print(
8930
8970
  "agent-launch: no agent-bios on PATH and no usable repo in "
8931
- f"corpus-status.json; run manually: agent-bios onboard --domains {domains}",
8971
+ f"corpus-status.json; run manually: agent-bios onboard {' '.join(interaction)} --domains {domains}",
8932
8972
  file=sys.stderr,
8933
8973
  )
8934
8974
  return
8935
- argv = ["bash", str(installer), "onboard", "--domains", domains]
8975
+ argv = ["bash", str(installer), "onboard", *interaction, "--domains", domains]
8936
8976
  print(f"\nagent-launch: applying corpus selection: {' '.join(argv)}\n", flush=True)
8937
8977
  proc = subprocess.run(argv)
8938
8978
  if proc.returncode != 0:
@@ -11008,7 +11048,7 @@ def _tolerate_narrow_stdout() -> None:
11008
11048
  pass
11009
11049
 
11010
11050
 
11011
- def main(argv: list[str]) -> int:
11051
+ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
11012
11052
  _tolerate_narrow_stdout()
11013
11053
  args = parse_args(drop_check_adapter_separator(argv))
11014
11054
  if args.corpus:
@@ -11142,14 +11182,18 @@ def main(argv: list[str]) -> int:
11142
11182
  and not rich_ui_declined
11143
11183
  and os.environ.get("TERM", "") not in {"", "dumb"}
11144
11184
  )
11145
- if use_textual and not textual_importable():
11146
- maybe_reexec_into_venv()
11185
+ if use_textual:
11186
+ bundled = bundled_ui and _activate_cli_ui_runtime()
11147
11187
  if not textual_importable():
11148
- use_textual = False
11149
- print(
11150
- "agent-launch: rich terminal UI unavailable; using numbered prompts.",
11151
- file=sys.stderr,
11152
- )
11188
+ if bundled:
11189
+ raise LaunchError("bundled UI APIs are unavailable; reinstall the agent-bios package")
11190
+ maybe_reexec_into_venv()
11191
+ if not textual_importable():
11192
+ use_textual = False
11193
+ print(
11194
+ "agent-launch: rich terminal UI unavailable; using numbered prompts.",
11195
+ file=sys.stderr,
11196
+ )
11153
11197
  while True:
11154
11198
  try:
11155
11199
  if args.understand:
@@ -11289,7 +11333,7 @@ def main(argv: list[str]) -> int:
11289
11333
 
11290
11334
  if __name__ == "__main__":
11291
11335
  try:
11292
- raise SystemExit(main(sys.argv[1:]))
11336
+ raise SystemExit(main(sys.argv[1:], bundled_ui=True))
11293
11337
  except LaunchError as exc:
11294
11338
  print(f"agent-launch: {exc}", file=sys.stderr)
11295
11339
  raise SystemExit(2)
@@ -1,28 +1,59 @@
1
1
  #!/usr/bin/env bash
2
- # Provision the managed virtualenv that backs the agent-launch Textual preflight.
3
- #
4
- # The interactive launcher re-execs into this venv (launch/agent-launch.py ->
5
- # maybe_reexec_into_venv). It is an enhancement, not a hard dependency: when the
6
- # venv is missing or broken, the launcher falls back to numbered prompts, and
7
- # every direct / non-TTY / --no-tui path keeps running under the system
8
- # interpreter untouched. Re-run this after upgrading Python or the pin below.
2
+ # Provision selected dependencies in the managed agent-bios virtualenv.
3
+ # No argument installs Textual for terminal interfaces. --learning-only installs
4
+ # the JSON Schema validator used by learn. Existing other packages are preserved.
9
5
  #
10
6
  # AGENT_LAUNCH_VENV override the venv location (default below)
11
7
  # AGENT_LAUNCH_PYTHON python used to create the venv (default: python3)
12
8
  set -eu
13
9
 
14
10
  TEXTUAL_PIN="textual==8.2.8"
11
+ JSONSCHEMA_PIN="jsonschema==4.26.0"
15
12
  VENV="${AGENT_LAUNCH_VENV:-$HOME/.local/share/agent-launch/venv}"
16
13
  PYTHON="${AGENT_LAUNCH_PYTHON:-python3}"
14
+ PACKAGES=("$TEXTUAL_PIN")
15
+ case "${1:-}" in
16
+ "") [ "$#" -eq 0 ] || { printf 'usage: provision-venv.sh [--learning-only]\n' >&2; exit 2; } ;;
17
+ --learning-only) [ "$#" -eq 1 ] || { printf 'usage: provision-venv.sh [--learning-only]\n' >&2; exit 2; }; PACKAGES=("$JSONSCHEMA_PIN") ;;
18
+ *) printf 'usage: provision-venv.sh [--learning-only]\n' >&2; exit 2 ;;
19
+ esac
20
+
21
+ runtime_python_ready() {
22
+ "$1" -c 'import sys; sys.exit("agent-bios requires Python 3.11+; select it with AGENT_LAUNCH_PYTHON and use a new AGENT_LAUNCH_VENV path for an incompatible existing environment" if sys.version_info < (3, 11) else 0)'
23
+ }
24
+
25
+ pinned_ready() {
26
+ "$VENV/bin/python" - "${PACKAGES[@]}" <<'PY'
27
+ import importlib
28
+ import importlib.metadata
29
+ import sys
30
+ for spec in sys.argv[1:]:
31
+ name, expected = spec.split("==", 1)
32
+ importlib.import_module(name)
33
+ if name == "jsonschema":
34
+ from jsonschema import Draft202012Validator
35
+ actual = importlib.metadata.version(name)
36
+ if actual != expected:
37
+ raise SystemExit(f"{name}: expected {expected}, found {actual}")
38
+ print(f" {name} {actual} | python {sys.version.split()[0]}")
39
+ PY
40
+ }
17
41
 
18
- if [ -x "$VENV/bin/python" ] && "$VENV/bin/python" -c 'import textual' 2>/dev/null; then
42
+ if [ -x "$VENV/bin/python" ]; then
43
+ runtime_python_ready "$VENV/bin/python"
44
+ else
45
+ runtime_python_ready "$PYTHON"
46
+ fi
47
+
48
+ if [ -x "$VENV/bin/python" ] && pinned_ready >/dev/null 2>&1; then
19
49
  printf 'agent-launch venv already provisioned: %s\n' "$VENV"
20
- "$VENV/bin/python" -c 'import textual, sys; print(" textual", textual.__version__, "| python", sys.version.split()[0])'
50
+ pinned_ready
21
51
  exit 0
22
52
  fi
23
53
 
24
54
  printf 'Provisioning agent-launch venv at %s ...\n' "$VENV"
25
- "$PYTHON" -m venv "$VENV"
26
- "$VENV/bin/python" -m pip install --quiet --upgrade pip
27
- "$VENV/bin/python" -m pip install --quiet "$TEXTUAL_PIN"
28
- "$VENV/bin/python" -c 'import textual, sys; print("Ready: textual", textual.__version__, "| python", sys.version.split()[0])'
55
+ if [ ! -x "$VENV/bin/python" ]; then
56
+ "$PYTHON" -m venv "$VENV"
57
+ fi
58
+ "$VENV/bin/python" -m pip install --quiet --force-reinstall "${PACKAGES[@]}"
59
+ pinned_ready
@@ -23,7 +23,9 @@ never patches the payload to make it pass (runtime enforces, does not reason).
23
23
 
24
24
  Input: the semantic payload as one JSON object on stdin.
25
25
  Host: --host claude|codex (default: the tool prefix of supporting_sessions[0]).
26
- Home: --config-dir, else $CLAUDE_CONFIG_DIR / $CODEX_HOME by host, else ~/.claude / ~/.codex.
26
+ Private storage: $AGENT_BIOS_CORPUS_DIR, else ~/.config/agent-bios/corpus.
27
+ Legacy storage: --config-dir, else $CLAUDE_CONFIG_DIR / $CODEX_HOME by host.
28
+ --config-dir requires AGENT_BIOS_LEGACY_INSTALL=1; it cannot relocate private capture.
27
29
  After the local writes it best-effort uploads not-yet-delivered records to
28
30
  {base}/api/ingest/learnings via a host-private watermark over events.jsonl. The base and
29
31
  token resolve from the agent-bios-owned slot ~/.config/agent-bios/{ingest-url,
@@ -298,12 +300,12 @@ def apply_codex(home, bullet, dry):
298
300
  APPLY = {"claude": apply_claude, "codex": apply_codex}
299
301
 
300
302
 
301
- # ── Phase 2 transport: watermark upload over the durable learnings.jsonl ───────
303
+ # ── Phase 2 transport: watermark upload over durable learning records ───────
302
304
  #
303
305
  # collect-learning runs on-demand (per `learn!`), so the upload piggybacks here:
304
306
  # after the local writes, best-effort POST any not-yet-settled records to
305
307
  # {ingest-url}/api/ingest/learnings and record which learning_ids are settled in
306
- # a small state file (the watermark). The durable learnings.jsonl is the single
308
+ # a small state file (the watermark). The durable event log is the single
307
309
  # source and is never capped, so nothing is lost across a multi-day server outage
308
310
  # — unsettled records simply retry on the next `learn!`. A 2xx (incl. a duplicate
309
311
  # re-send, which the server dedups by learning_id) settles a record; 400/413
@@ -1143,11 +1145,13 @@ def _self_test():
1143
1145
 
1144
1146
 
1145
1147
  def main():
1148
+ sys.dont_write_bytecode = True
1146
1149
  ap = argparse.ArgumentParser(description="Submit a session learning (learn!).")
1147
1150
  ap.add_argument("--host", choices=sorted(HOSTS),
1148
1151
  help="session host (default: tool prefix of supporting_sessions[0])")
1149
1152
  ap.add_argument("--config-dir", default=None,
1150
- help="config home (default: $CLAUDE_CONFIG_DIR / $CODEX_HOME by host)")
1153
+ help="legacy host home only (requires AGENT_BIOS_LEGACY_INSTALL=1); "
1154
+ "private capture uses AGENT_BIOS_CORPUS_DIR")
1151
1155
  ap.add_argument("--dry-run", action="store_true",
1152
1156
  help="validate and print actions; write nothing")
1153
1157
  ap.add_argument("--no-upload", action="store_true",
@@ -1160,6 +1164,11 @@ def main():
1160
1164
  _self_test()
1161
1165
  return
1162
1166
 
1167
+ legacy = os.environ.get("AGENT_BIOS_LEGACY_INSTALL") == "1"
1168
+ if args.config_dir is not None and not legacy:
1169
+ ap.error("--config-dir applies only to AGENT_BIOS_LEGACY_INSTALL=1; "
1170
+ "set AGENT_BIOS_CORPUS_DIR to relocate private learning storage")
1171
+
1163
1172
  payload = read_payload()
1164
1173
  host = resolve_host(args.host, payload)
1165
1174
  home = resolve_home(host, args.config_dir)
@@ -1173,7 +1182,7 @@ def main():
1173
1182
  sys.exit(1)
1174
1183
 
1175
1184
  dry = args.dry_run
1176
- if os.environ.get("AGENT_BIOS_LEGACY_INSTALL") != "1":
1185
+ if not legacy:
1177
1186
  # Capture is an immutable private source. Model-visible projections are
1178
1187
  # composed when an activated session is started, never written globally.
1179
1188
  sys.path.insert(0, str(REPO / "compose"))
@@ -21,14 +21,14 @@
21
21
  "learning_id": {
22
22
  "type": "string",
23
23
  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
24
- "description": "Client-generated UUID for this learning, lowercase canonical form (tooling must lowercase, e.g. macOS uuidgen emits uppercase). Owned by learn/collect-learning.py, never hand-authored. Idempotency key for the pending-learnings.jsonl bounded retry in Phase 2. Distinct from the ledger's `id` (a curator-assigned short id like 'S3-07'); this is the light-flow record's own key.",
24
+ "description": "Client-generated UUID for this learning, lowercase canonical form (tooling must lowercase, e.g. macOS uuidgen emits uppercase). Owned by learn/collect-learning.py, never hand-authored. Idempotency key for the bounded upload retry over the host-qualified events.jsonl. Distinct from the ledger's `id` (a curator-assigned short id like 'S3-07'); this is the light-flow record's own key.",
25
25
  "$comment": "Ledger entries use `id`; the light-flow record's own key is `learning_id` (the artifact is a learning — LEXICON.md — so it is not `distill_id`, which would collide with the heavy session-distill pipeline)."
26
26
  },
27
27
  "lesson": {
28
28
  "type": "string",
29
29
  "minLength": 8,
30
30
  "maxLength": 2000,
31
- "description": "The learning as prose the same text that landed in the user's ~/.claude/personal/learnings.md. Ledger-compatible with entries[].lesson."
31
+ "description": "The captured lesson prose stored in the private corpus's host-qualified events.jsonl and supplied to future selected, activated-session snapshots. Ledger-compatible with entries[].lesson."
32
32
  },
33
33
  "domain": {
34
34
  "type": "string",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-bios",
3
- "version": "0.18.0",
4
- "releaseDate": "2026-09-11",
3
+ "version": "0.19.0",
4
+ "releaseDate": "2026-09-13",
5
5
  "description": "A thin, low-level instruction layer for LLM CLI agents: one set of principles and behavior whichever model you run. Stores a private, editable corpus for explicitly activated sessions.",
6
6
  "bin": {
7
7
  "agent-bios": "install.sh"
@@ -39,9 +39,19 @@
39
39
  "compose/corpus_catalog.py",
40
40
  "compose/corpus_session.py",
41
41
  "compose/corpus_install.py",
42
+ "compose/corpus_setup.py",
43
+ "compose/corpus_setup_cli.py",
44
+ "compose/corpus_setup_i18n.py",
45
+ "compose/corpus_setup_ui.py",
46
+ "compose/corpus_ui_runtime.py",
47
+ "compose/ui_runtime/",
48
+ "compose/corpus_import.py",
49
+ "compose/corpus_app.py",
50
+ "compose/app_bridge/",
42
51
  "compose/corpus_transaction.py",
43
52
  "compose/corpus_understand.py",
44
53
  "compose/bootstrap/",
54
+ "compose/setup/START.md",
45
55
  "launch/check-prompting-targets.sh",
46
56
  "learn/check-learning.py",
47
57
  "learn/collect-learning.py",
@@ -53,7 +63,9 @@
53
63
  "install.sh",
54
64
  "launch/provision-venv.sh",
55
65
  "session-cost.py",
66
+ "docs/",
56
67
  "README.md",
68
+ "INSTALL.md",
57
69
  "DEPENDENCIES.md",
58
70
  "provenance.json"
59
71
  ],
package/provenance.json CHANGED
@@ -1 +1 @@
1
- {"commit":"588260e5b60f52f0f58b47e8d2ef7ae567b382a6","committedAt":"2026-09-11T21:09:27+09:00","dirty":false}
1
+ {"commit":"35c75ca6ee0c7ecd3ed67c58dc48b854bc2d8b8e","committedAt":"2026-09-13T14:12:08+09:00","dirty":false}