agent-bios 0.19.1 → 0.19.3

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 (106) hide show
  1. package/DEPENDENCIES.md +58 -30
  2. package/INSTALL.md +4 -4
  3. package/README.md +105 -28
  4. package/claude/CLAUDE.md +1 -1
  5. package/claude/guides/claude-prompting.md +1 -1
  6. package/claude/guides/cli-multi-model-workflow.md +4 -4
  7. package/claude/guides/coding-staged-workflow.md +17 -0
  8. package/claude/guides/documentation-hygiene.md +3 -0
  9. package/claude/guides/gpt-prompting.md +1 -1
  10. package/claude/guides/korean-writing.md +153 -0
  11. package/claude/guides/learning-flow.md +4 -4
  12. package/claude/guides/llm-capability-boundary.md +7 -1
  13. package/claude/guides/session-distill-workflow.md +8 -8
  14. package/claude/guides/slide-writing/RUNBOOK.md +5 -5
  15. package/claude/guides/tooling-gotchas.md +20 -1
  16. package/claude/guides/ui-design/visual-direction.md +88 -0
  17. package/claude/guides/ui-design.md +90 -0
  18. package/claude/guides/verification-discipline.md +10 -1
  19. package/claude/hooks/tooling-gotchas-hook.py +41 -0
  20. package/claude/skills/repo-charter/SKILL.md +3 -3
  21. package/claude/skills/understand/SKILL.md +5 -5
  22. package/codex/AGENTS.md +1 -1
  23. package/codex/guides/claude-prompting.md +1 -1
  24. package/codex/guides/cli-multi-model-workflow.md +4 -4
  25. package/codex/guides/coding-staged-workflow.md +17 -0
  26. package/codex/guides/documentation-hygiene.md +3 -0
  27. package/codex/guides/gpt-prompting.md +1 -1
  28. package/codex/guides/korean-writing.md +153 -0
  29. package/codex/guides/learning-flow.md +4 -4
  30. package/codex/guides/llm-capability-boundary.md +7 -1
  31. package/codex/guides/session-distill-workflow.md +8 -8
  32. package/codex/guides/slide-writing/RUNBOOK.md +5 -5
  33. package/codex/guides/tooling-gotchas.md +20 -1
  34. package/codex/guides/ui-design/visual-direction.md +88 -0
  35. package/codex/guides/ui-design.md +90 -0
  36. package/codex/guides/verification-discipline.md +10 -1
  37. package/compose/app_bridge/SKILL.md +12 -12
  38. package/compose/app_bridge/scripts/bridge.py +35 -10
  39. package/compose/app_desktop/server.py +250 -0
  40. package/compose/assemble.py +5 -5
  41. package/compose/bootstrap/SKILL.md +18 -18
  42. package/compose/canary.sh +4 -4
  43. package/compose/check-domains.py +6 -6
  44. package/compose/corpus-state.py +16 -1168
  45. package/compose/corpus.py +13 -402
  46. package/compose/corpus_app.py +14 -450
  47. package/compose/corpus_catalog.py +15 -926
  48. package/compose/corpus_import.py +14 -523
  49. package/compose/corpus_install.py +14 -1847
  50. package/compose/corpus_session.py +16 -848
  51. package/compose/corpus_setup.py +16 -672
  52. package/compose/corpus_setup_cli.py +15 -580
  53. package/compose/corpus_setup_i18n.py +20 -324
  54. package/compose/corpus_setup_ui.py +18 -645
  55. package/compose/corpus_store.py +16 -1664
  56. package/compose/corpus_transaction.py +15 -284
  57. package/compose/corpus_ui.py +17 -972
  58. package/compose/corpus_ui_runtime.py +16 -274
  59. package/compose/corpus_understand.py +13 -671
  60. package/compose/domains.json +3 -1
  61. package/compose/host_platform.py +121 -0
  62. package/compose/instructions-state.py +1178 -0
  63. package/compose/instructions.py +409 -0
  64. package/compose/instructions_app.py +697 -0
  65. package/compose/instructions_catalog.py +931 -0
  66. package/compose/instructions_import.py +537 -0
  67. package/compose/instructions_install.py +1932 -0
  68. package/compose/instructions_session.py +852 -0
  69. package/compose/instructions_setup.py +713 -0
  70. package/compose/instructions_setup_cli.py +607 -0
  71. package/compose/instructions_setup_i18n.py +327 -0
  72. package/compose/instructions_setup_ui.py +647 -0
  73. package/compose/instructions_store.py +1668 -0
  74. package/compose/instructions_transaction.py +308 -0
  75. package/compose/instructions_ui.py +975 -0
  76. package/compose/instructions_ui_runtime.py +279 -0
  77. package/compose/instructions_understand.py +678 -0
  78. package/compose/native_cli.py +52 -0
  79. package/compose/register-hooks.py +1 -1
  80. package/compose/runtime_entry.py +58 -0
  81. package/compose/setup/START.md +11 -11
  82. package/compose/windows_deploy.py +719 -0
  83. package/docs/advanced-launch.md +11 -11
  84. package/docs/instructions-compatibility.md +86 -0
  85. package/docs/{corpus.md → instructions.md} +36 -8
  86. package/docs/recovery.md +10 -10
  87. package/docs/releases/0.19.2.md +38 -0
  88. package/docs/releases/0.19.3.md +107 -0
  89. package/docs/session-model.md +31 -20
  90. package/docs/setup.md +63 -26
  91. package/docs/understand.md +6 -6
  92. package/docs/windows.md +99 -0
  93. package/install.sh +71 -69
  94. package/launch/agent-launch.py +309 -293
  95. package/launch/agent-launch.toml +2 -2
  96. package/launch/agent-launch.zsh +11 -1
  97. package/launch/i18n/en.toml +55 -55
  98. package/launch/i18n/ja.toml +56 -56
  99. package/launch/i18n/ko.toml +56 -56
  100. package/launch/shell_integration.py +4 -4
  101. package/learn/collect-learning.py +10 -10
  102. package/learn/learning.schema.json +1 -1
  103. package/learn/migrate-learnings.py +51 -51
  104. package/package.json +33 -12
  105. package/provenance.json +1 -1
  106. /package/docs/assets/{corpus-studio.svg → instructions-studio.svg} +0 -0
@@ -6,7 +6,6 @@ from __future__ import annotations
6
6
  import argparse
7
7
  import copy
8
8
  import datetime
9
- import fcntl
10
9
  import hashlib
11
10
  import json
12
11
  import os
@@ -24,6 +23,12 @@ import uuid
24
23
  from dataclasses import dataclass, field
25
24
  from typing import Any, Callable, NoReturn
26
25
 
26
+ if os.name == "nt":
27
+ sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "compose"))
28
+ from host_platform import file_locks as fcntl
29
+ else:
30
+ import fcntl
31
+
27
32
 
28
33
  TIER_ORDER = ("frontier", "helm", "workhorse", "sweep")
29
34
  # Review is not work any tier can do. A reviewer must be at least this capable; helm or
@@ -972,10 +977,10 @@ CRITERION_CLAUSE = (
972
977
  f"refuses unclassified findings, and fold returned rows against the declared enum "
973
978
  f"without guessing a class"
974
979
  )
975
- # The panel that carries corpus/registration text, and the scrolling body that holds it.
980
+ # The panel that carries instructions/registration text, and the scrolling body that holds it.
976
981
  # Named once so the screen, its CSS, its key bindings and the check that drives them cannot
977
982
  # drift apart.
978
- CORPUS_PANEL_ID = "al-corpus"
983
+ INSTRUCTIONS_PANEL_ID = "al-instructions"
979
984
  BODY_PANEL_ID = "al-body"
980
985
  TROPHY_PANEL_ID = "al-understand-trophy"
981
986
 
@@ -1423,8 +1428,8 @@ def parse_capability_offers(name: str, raw: Any) -> tuple:
1423
1428
 
1424
1429
  def review_wrapper_command(host: str, name: str) -> str | None:
1425
1430
  """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"
1431
+ if private_instructions_enabled():
1432
+ path = instructions_package_root() / "wrappers" / f"{name}.sh"
1428
1433
  elif host == "codex":
1429
1434
  path = expand_config_path(f"${{CODEX_HOME}}/bin/{name}")
1430
1435
  else:
@@ -3635,7 +3640,7 @@ def emit_receipt_command(
3635
3640
  fields[key] = value
3636
3641
  # Present-but-empty is a configuration error, never a silent prose downgrade: an
3637
3642
  # exported empty value made the whole validation vacuous while the caller believed
3638
- # it armed (criterion round, #3 — the empty-variable class the corpus already
3643
+ # it armed (criterion round, #3 — the empty-variable class the instructions already
3639
3644
  # names for shell checks).
3640
3645
  criterion_env = os.environ.get(RECEIPT_CRITERION_ENV)
3641
3646
  if criterion_env is not None and not criterion_env.strip():
@@ -4123,44 +4128,54 @@ _UI_RUNTIME_RELEASE: Callable[[], None] | None = None
4123
4128
  def exec_backend(command: str, args: list[str], env: dict[str, str] | None = None) -> NoReturn:
4124
4129
  if _UI_RUNTIME_RELEASE is not None:
4125
4130
  _UI_RUNTIME_RELEASE()
4131
+ if os.name == "nt":
4132
+ raise SystemExit(subprocess.call([command, *args], env=os.environ.copy() if env is None else env))
4126
4133
  os.execve(command, [command, *args], os.environ.copy() if env is None else env)
4127
4134
 
4128
4135
 
4129
- def private_corpus_enabled() -> bool:
4130
- explicit = os.environ.get("AGENT_BIOS_PRIVATE_CORPUS")
4136
+ def _instructions_environment(canonical: str, legacy: str, default=None):
4137
+ # The launcher also runs standalone, before any private package is located.
4138
+ current, previous = os.environ.get(canonical), os.environ.get(legacy)
4139
+ if current is not None and previous is not None and current != previous:
4140
+ raise LaunchError(f"conflicting {canonical} and {legacy}; set one value")
4141
+ return current if current is not None else previous if previous is not None else default
4142
+
4143
+
4144
+ def private_instructions_enabled() -> bool:
4145
+ explicit = _instructions_environment("AGENT_BIOS_PRIVATE_INSTRUCTIONS", "AGENT_BIOS_PRIVATE_CORPUS")
4131
4146
  if explicit is not None:
4132
4147
  return explicit == "1"
4133
4148
  state = pathlib.Path(os.environ.get("AGENT_BIOS_STATE_DIR", str(pathlib.Path.home() / ".local/share/agent-bios")))
4134
4149
  return (state / "runtime/private-install.json").is_file()
4135
4150
 
4136
4151
 
4137
- def corpus_package_root() -> pathlib.Path:
4152
+ def instructions_package_root() -> pathlib.Path:
4138
4153
  explicit = os.environ.get("AGENT_BIOS_PACKAGE_ROOT")
4139
4154
  if explicit:
4140
4155
  return pathlib.Path(explicit).resolve()
4141
4156
  source = pathlib.Path(__file__).resolve().parents[1]
4142
- if (source / "compose/corpus_store.py").is_file():
4157
+ if any((source / "compose" / name).is_file() for name in ("instructions_store.py", "corpus_store.py")):
4143
4158
  return source
4144
4159
  state = pathlib.Path(os.environ.get("AGENT_BIOS_STATE_DIR", str(pathlib.Path.home() / ".local/share/agent-bios")))
4145
4160
  try:
4146
4161
  root = pathlib.Path(json.loads((state / "runtime/private-install.json").read_text())["package_root"])
4147
4162
  except (OSError, ValueError, KeyError, TypeError) as exc:
4148
- raise LaunchError("private corpus runtime missing; run agent-bios install") from exc
4149
- if not (root / "compose/corpus_store.py").is_file():
4150
- raise LaunchError("private corpus runtime missing; run agent-bios install")
4163
+ raise LaunchError("private instructions runtime missing; run agent-bios install") from exc
4164
+ if not any((root / "compose" / name).is_file() for name in ("instructions_store.py", "corpus_store.py")):
4165
+ raise LaunchError("private instructions runtime missing; run agent-bios install")
4151
4166
  return root
4152
4167
 
4153
4168
 
4154
- def corpus_store():
4155
- root = corpus_package_root()
4169
+ def instructions_store():
4170
+ root = instructions_package_root()
4156
4171
  module_root = str(root / "compose")
4157
4172
  if module_root not in sys.path:
4158
4173
  sys.path.insert(0, module_root)
4159
- from corpus_store import CorpusStore
4160
- return CorpusStore(root)
4174
+ from instructions_store import InstructionsStore
4175
+ return InstructionsStore(root)
4161
4176
 
4162
4177
 
4163
- def _corpus_generation(state_root: pathlib.Path, config_path: pathlib.Path) -> str:
4178
+ def _instructions_generation(state_root: pathlib.Path, config_path: pathlib.Path) -> str:
4164
4179
  paths = {state_root / "runtime/private-install.json", state_root / "runtime/state.json", config_path}
4165
4180
  digest = hashlib.sha256()
4166
4181
  for path in sorted(paths):
@@ -4172,12 +4187,12 @@ def _corpus_generation(state_root: pathlib.Path, config_path: pathlib.Path) -> s
4172
4187
 
4173
4188
  def _load_private_config(config_path: pathlib.Path, *, replay_only: bool = False):
4174
4189
  """Read one coherent configuration; verify its generation again at activation."""
4175
- root = corpus_package_root()
4190
+ root = instructions_package_root()
4176
4191
  module_root = str(root / "compose")
4177
4192
  if module_root not in sys.path:
4178
4193
  sys.path.insert(0, module_root)
4179
- from corpus_transaction import transaction_lock, guard_pending, confirmed_release, TransactionPendingError
4180
- state_root = corpus_store().state_root
4194
+ from instructions_transaction import transaction_lock, guard_pending, confirmed_release, TransactionPendingError
4195
+ state_root = instructions_store().state_root
4181
4196
  with transaction_lock(state_root):
4182
4197
  try:
4183
4198
  guard_pending(state_root)
@@ -4186,36 +4201,37 @@ def _load_private_config(config_path: pathlib.Path, *, replay_only: bool = False
4186
4201
  raise
4187
4202
  config_path = confirmed_release(state_root) / "launch/agent-launch.toml"
4188
4203
  config = load_config(config_path)
4189
- return config, _corpus_generation(state_root, config_path)
4204
+ return config, _instructions_generation(state_root, config_path)
4190
4205
 
4191
4206
 
4192
4207
  def _snapshot_from_config(store, config_path, generation, host, selected, *, dry_run, native):
4193
- from corpus_transaction import transaction_lock, guard_pending
4208
+ from instructions_transaction import transaction_lock, guard_pending
4194
4209
  with transaction_lock(store.state_root):
4195
4210
  guard_pending(store.state_root)
4196
- if generation != _corpus_generation(store.state_root, config_path):
4197
- raise LaunchError("corpus installation or launch settings changed during setup; reopen the launcher")
4211
+ if generation != _instructions_generation(store.state_root, config_path):
4212
+ raise LaunchError("instructions installation or launch settings changed during setup; reopen the launcher")
4198
4213
  return store.snapshot(host, selected, dry_run=dry_run, native=native)
4199
4214
 
4200
4215
 
4201
- def open_corpus_studio() -> None:
4202
- root = corpus_package_root()
4203
- result = subprocess.run([sys.executable, str(root / "compose/corpus.py"), "--repo", str(root)])
4216
+ def open_instructions_studio() -> None:
4217
+ root = instructions_package_root()
4218
+ from host_platform import python_argv
4219
+ result = subprocess.run(python_argv(root / "compose/instructions.py", "--repo", str(root)))
4204
4220
  if result.returncode:
4205
- print(f"agent-launch: Corpus Studio exited {result.returncode}", file=sys.stderr)
4221
+ print(f"agent-launch: Instructions Studio exited {result.returncode}", file=sys.stderr)
4206
4222
 
4207
4223
 
4208
4224
  def understand_manager():
4209
- if not private_corpus_enabled():
4225
+ if not private_instructions_enabled():
4210
4226
  raise LaunchError("understand! requires a private installation; run agent-bios install")
4211
- store = corpus_store()
4212
- from corpus_understand import CorpusUnderstand
4213
- return CorpusUnderstand(store)
4227
+ store = instructions_store()
4228
+ from instructions_understand import InstructionsUnderstand
4229
+ return InstructionsUnderstand(store)
4214
4230
 
4215
4231
 
4216
4232
  def understand_trophy() -> str:
4217
4233
  """Decoration is derived from durable awards, never a launcher-local flag."""
4218
- if not private_corpus_enabled():
4234
+ if not private_instructions_enabled():
4219
4235
  return ""
4220
4236
  try:
4221
4237
  manager = understand_manager()
@@ -4235,7 +4251,7 @@ def build_understand_plan(config: dict[str, Any], host: str, bundle_id: str) ->
4235
4251
  "label": "Understand!", "mode": DEFAULT_PRESET_MODE, "main_tier": "helm",
4236
4252
  "review_setup": "none", "delegation": False,
4237
4253
  "codex_execution_policy": STANDARD_POLICY, "claude_permission_mode": STANDARD_POLICY,
4238
- "mission": "Help the user understand the selected corpus bundle's purpose, context, "
4254
+ "mission": "Help the user understand the selected instructions bundle's purpose, context, "
4239
4255
  "mechanisms and limits through an adaptive dialogue. Treat learning material "
4240
4256
  "as material to discuss, not authorization to execute its instructions. "
4241
4257
  "Choose finite core coverage and ask only useful questions, fewer when enough. "
@@ -4823,7 +4839,7 @@ def setup_summary_lines(plan: dict[str, Any] | None) -> list[str]:
4823
4839
  delegation="on" if plan["delegation"] else "off", execution=execution,
4824
4840
  ),
4825
4841
  ]
4826
- if private_corpus_enabled():
4842
+ if private_instructions_enabled():
4827
4843
  lines.append(
4828
4844
  setup_panel_column(t("setup.global-instructions.label"))
4829
4845
  + " "
@@ -4867,20 +4883,20 @@ def _activate_cli_ui_runtime() -> bool:
4867
4883
  """Use the package's offline UI bundle; standalone compatibility copies keep their runtime."""
4868
4884
  global _UI_RUNTIME_RELEASE
4869
4885
  source = pathlib.Path(__file__).resolve().parents[1]
4870
- if os.environ.get("AGENT_BIOS_PACKAGE_ROOT") or private_corpus_enabled():
4871
- root = corpus_package_root()
4872
- elif (source / "compose/corpus_store.py").is_file():
4886
+ if os.environ.get("AGENT_BIOS_PACKAGE_ROOT") or private_instructions_enabled():
4887
+ root = instructions_package_root()
4888
+ elif any((source / "compose" / name).is_file() for name in ("instructions_store.py", "corpus_store.py")):
4873
4889
  root = source
4874
4890
  else:
4875
4891
  return False
4876
- loader = root / "compose/corpus_ui_runtime.py"
4892
+ loader = root / "compose/instructions_ui_runtime.py"
4877
4893
  if loader.is_symlink() or not loader.is_file():
4878
4894
  raise LaunchError("bundled UI runtime loader is missing or unsafe; reinstall the agent-bios package")
4879
4895
  module_root = str(root / "compose")
4880
4896
  if module_root not in sys.path:
4881
4897
  sys.path.insert(0, module_root)
4882
4898
  try:
4883
- from corpus_ui_runtime import activate_ui_runtime, release_ui_runtime
4899
+ from instructions_ui_runtime import activate_ui_runtime, release_ui_runtime
4884
4900
  activate_ui_runtime(root)
4885
4901
  except (ImportError, OSError, RuntimeError) as exc:
4886
4902
  raise LaunchError(f"bundled terminal UI could not start: {exc}") from exc
@@ -5033,10 +5049,10 @@ def _build_app_class():
5033
5049
  height: auto; min-height: 5; max-height: 45vh;
5034
5050
  }
5035
5051
  #al-detail-scroll > #al-detail { border: none; padding: 0; height: auto; }
5036
- #al-corpus-title { background: $warning; color: black; text-style: bold; padding: 0 1; }
5052
+ #al-instructions-title { background: $warning; color: black; text-style: bold; padding: 0 1; }
5037
5053
  /* No cap and no scroller of its own: one nested scroll region inside another is a
5038
5054
  worse answer than a body that simply scrolls. */
5039
- #al-corpus {
5055
+ #al-instructions {
5040
5056
  border: round $warning; padding: 0 1; height: auto;
5041
5057
  }
5042
5058
  #al-hdr { color: $text-muted; text-style: bold; padding: 0 1; }
@@ -5121,7 +5137,7 @@ def _build_app_class():
5121
5137
  self.call_after_refresh(self._size_detail)
5122
5138
 
5123
5139
  def __init__(
5124
- self, title, options, default, allow_back, plan, preview=None, corpus=None,
5140
+ self, title, options, default, allow_back, plan, preview=None, instructions=None,
5125
5141
  confirm=None,
5126
5142
  ):
5127
5143
  super().__init__()
@@ -5131,7 +5147,7 @@ def _build_app_class():
5131
5147
  self._allow_back = allow_back
5132
5148
  self._plan = plan
5133
5149
  self._preview = preview
5134
- self._corpus = corpus
5150
+ self._instructions = instructions
5135
5151
  self._trophy = understand_trophy()
5136
5152
  # The value Enter decides on, for a screen whose rows are changes rather than
5137
5153
  # choices. Without it Enter and Space would both mean "act on the highlighted
@@ -5148,9 +5164,9 @@ def _build_app_class():
5148
5164
  with Horizontal(id="al-reference-row"):
5149
5165
  with Vertical(id="al-reference-content"):
5150
5166
  yield setup_panel(self._plan)
5151
- if self._corpus:
5152
- yield Static(t("tui.corpus.title"), id="al-corpus-title")
5153
- yield Static("\n".join(self._corpus), id=CORPUS_PANEL_ID)
5167
+ if self._instructions:
5168
+ yield Static(t("tui.instructions.title"), id="al-instructions-title")
5169
+ yield Static("\n".join(self._instructions), id=INSTRUCTIONS_PANEL_ID)
5154
5170
  yield Static(self._trophy, id=TROPHY_PANEL_ID, markup=False)
5155
5171
  detail = VerticalScroll(Static("", id="al-detail", markup=False), id="al-detail-scroll")
5156
5172
  detail.border_title = t("tui.detail.title")
@@ -5357,13 +5373,13 @@ class TextualUI:
5357
5373
  default: str,
5358
5374
  allow_back: bool,
5359
5375
  preview=None,
5360
- corpus_lines: list[str] | None = None,
5376
+ instructions_lines: list[str] | None = None,
5361
5377
  confirm: str | None = None,
5362
5378
  ) -> str:
5363
5379
  result = self.app.call_from_thread(
5364
5380
  self.app.push_screen_wait,
5365
5381
  self._menu_screen(
5366
- title, options, default, allow_back, self.plan, preview, corpus_lines,
5382
+ title, options, default, allow_back, self.plan, preview, instructions_lines,
5367
5383
  confirm,
5368
5384
  ),
5369
5385
  )
@@ -5429,12 +5445,12 @@ def choose_lines(
5429
5445
  options: list[MenuOption],
5430
5446
  default: str,
5431
5447
  allow_back: bool,
5432
- corpus_lines: list[str] | None = None,
5448
+ instructions_lines: list[str] | None = None,
5433
5449
  ) -> str:
5434
5450
  print(f"\n{title}")
5435
- if corpus_lines:
5436
- print(" " + t("prompt.corpus.header"))
5437
- for line in corpus_lines:
5451
+ if instructions_lines:
5452
+ print(" " + t("prompt.instructions.header"))
5453
+ for line in instructions_lines:
5438
5454
  print(f" {line}")
5439
5455
  print(" --")
5440
5456
  for index, option in enumerate(options, 1):
@@ -5480,16 +5496,16 @@ def choose(
5480
5496
  ui: TextualUI | None = None,
5481
5497
  allow_back: bool = False,
5482
5498
  preview=None,
5483
- corpus_lines: list[str] | None = None,
5499
+ instructions_lines: list[str] | None = None,
5484
5500
  confirm: str | None = None,
5485
5501
  ) -> str:
5486
5502
  if not any(option.enabled for option in options):
5487
5503
  raise LaunchError(f"no available options for {title}")
5488
5504
  if ui is not None:
5489
5505
  return ui.choose(
5490
- title, options, default, allow_back, preview, corpus_lines, confirm
5506
+ title, options, default, allow_back, preview, instructions_lines, confirm
5491
5507
  )
5492
- return choose_lines(title, options, default, allow_back, corpus_lines)
5508
+ return choose_lines(title, options, default, allow_back, instructions_lines)
5493
5509
 
5494
5510
 
5495
5511
  def read_input(prompt: str) -> str:
@@ -6530,7 +6546,7 @@ def register_reviewer_wizard(
6530
6546
  if perspectives:
6531
6547
  answers["perspectives"] = perspectives
6532
6548
  break
6533
- _corpus_info(ui, t("wizard.title"), [t("wizard.perspectives.refused")])
6549
+ _instructions_info(ui, t("wizard.title"), [t("wizard.perspectives.refused")])
6534
6550
  while True:
6535
6551
  trials_raw = prompt_text(
6536
6552
  t("wizard.trials.label"), str(answers.get("trials", 2)), ui
@@ -6538,10 +6554,10 @@ def register_reviewer_wizard(
6538
6554
  try:
6539
6555
  trials = int(trials_raw)
6540
6556
  except ValueError:
6541
- _corpus_info(ui, t("wizard.title"), [t("wizard.trials.refused")])
6557
+ _instructions_info(ui, t("wizard.title"), [t("wizard.trials.refused")])
6542
6558
  continue
6543
6559
  if trials < 1:
6544
- _corpus_info(ui, t("wizard.title"), [t("wizard.trials.refused")])
6560
+ _instructions_info(ui, t("wizard.title"), [t("wizard.trials.refused")])
6545
6561
  continue
6546
6562
  answers["trials"] = trials
6547
6563
  break
@@ -6596,7 +6612,7 @@ def register_reviewer_wizard(
6596
6612
  MenuOption("edit", t("wizard.confirm.edit.label"), ""),
6597
6613
  ],
6598
6614
  "write", ui, allow_back=True,
6599
- corpus_lines=[t("wizard.confirm.target").format(target=target),
6615
+ instructions_lines=[t("wizard.confirm.target").format(target=target),
6600
6616
  *block.splitlines()],
6601
6617
  )
6602
6618
  if confirm == "edit":
@@ -6610,7 +6626,7 @@ def register_reviewer_wizard(
6610
6626
  # a planted symlink and truncates whatever it points at, merely by
6611
6627
  # opening the wizard.
6612
6628
  lock_fd = os.open(
6613
- lock_path, os.O_CREAT | os.O_WRONLY | os.O_NOFOLLOW, 0o600
6629
+ lock_path, os.O_CREAT | os.O_WRONLY | getattr(os, "O_NOFOLLOW", 0), 0o600
6614
6630
  )
6615
6631
  with os.fdopen(lock_fd, "w") as lock_handle:
6616
6632
  fcntl.flock(lock_handle, fcntl.LOCK_EX)
@@ -6618,16 +6634,16 @@ def register_reviewer_wizard(
6618
6634
  # since the trial must read as a race even when its bytes happen
6619
6635
  # to equal the header the wizard would have invented.
6620
6636
  if write_target.is_file() != existed:
6621
- _corpus_info(ui, t("wizard.title"), [t("wizard.raced.line")],
6622
- back_hint=t("corpus.back.hint"))
6637
+ _instructions_info(ui, t("wizard.title"), [t("wizard.raced.line")],
6638
+ back_hint=t("instructions.back.hint"))
6623
6639
  return False
6624
6640
  current = (
6625
6641
  write_target.read_bytes() if write_target.is_file()
6626
6642
  else USER_METHODS_HEADER.encode()
6627
6643
  )
6628
6644
  if current != before:
6629
- _corpus_info(ui, t("wizard.title"), [t("wizard.raced.line")],
6630
- back_hint=t("corpus.back.hint"))
6645
+ _instructions_info(ui, t("wizard.title"), [t("wizard.raced.line")],
6646
+ back_hint=t("instructions.back.hint"))
6631
6647
  return False
6632
6648
  mode = (write_target.stat().st_mode & 0o777) if existed else 0o600
6633
6649
  try:
@@ -6663,9 +6679,9 @@ def register_reviewer_wizard(
6663
6679
  # merge. Sharing the restore branch's message told the user the
6664
6680
  # file had been restored in the one case where it was left
6665
6681
  # exactly as the other writer wrote it.
6666
- _corpus_info(
6682
+ _instructions_info(
6667
6683
  ui, t("wizard.title"), [t("wizard.postwrite.raced.line")],
6668
- back_hint=t("corpus.back.hint"),
6684
+ back_hint=t("instructions.back.hint"),
6669
6685
  )
6670
6686
  return False
6671
6687
  if existed:
@@ -6677,18 +6693,18 @@ def register_reviewer_wizard(
6677
6693
  os.replace(restore, write_target)
6678
6694
  else:
6679
6695
  write_target.unlink(missing_ok=True)
6680
- _corpus_info(ui, t("wizard.title"), [t("wizard.postwrite.line")],
6681
- back_hint=t("corpus.back.hint"))
6696
+ _instructions_info(ui, t("wizard.title"), [t("wizard.postwrite.line")],
6697
+ back_hint=t("instructions.back.hint"))
6682
6698
  return False # pre-write state restored above
6683
6699
  if verdict is None:
6684
6700
  config.clear()
6685
6701
  config.update(fresh)
6686
6702
  registry.clear()
6687
6703
  registry.update(load_review_methods(config))
6688
- _corpus_info(
6704
+ _instructions_info(
6689
6705
  ui, t("wizard.title"),
6690
6706
  [t("wizard.done.line").format(method_id=answers["id"])],
6691
- back_hint=t("corpus.back.hint"),
6707
+ back_hint=t("instructions.back.hint"),
6692
6708
  )
6693
6709
  return True
6694
6710
  action = choose(
@@ -6699,7 +6715,7 @@ def register_reviewer_wizard(
6699
6715
  MenuOption("contract", t("wizard.refused.contract.label"), ""),
6700
6716
  ],
6701
6717
  "edit", ui, allow_back=True,
6702
- corpus_lines=[t("wizard.refused.header"), "", *verdict.splitlines()],
6718
+ instructions_lines=[t("wizard.refused.header"), "", *verdict.splitlines()],
6703
6719
  )
6704
6720
  if action == "contract":
6705
6721
  register_reviewer_info(ui, config_path)
@@ -6712,7 +6728,7 @@ def register_reviewer_info(ui: TextualUI | None, config_path: pathlib.Path) -> N
6712
6728
  user owns and what a descriptor must contain, rather than offering a form that
6713
6729
  would have to duplicate the reader's validation and then drift from it."""
6714
6730
  path = user_methods_path(config_path)
6715
- _corpus_info(
6731
+ _instructions_info(
6716
6732
  ui,
6717
6733
  "Register another reviewer",
6718
6734
  [
@@ -6800,7 +6816,7 @@ def review_editor(
6800
6816
  except BackRequested:
6801
6817
  return None
6802
6818
  except LaunchError as exc:
6803
- _corpus_info(
6819
+ _instructions_info(
6804
6820
  ui, f"{title} — cannot be seated", [str(exc)],
6805
6821
  back_hint="Return to the review editor.",
6806
6822
  )
@@ -7659,7 +7675,7 @@ def save_preset(
7659
7675
  try:
7660
7676
  target.parent.mkdir(parents=True, exist_ok=True)
7661
7677
  lock_path = target.with_name(target.name + ".lock")
7662
- lock_fd = os.open(lock_path, os.O_CREAT | os.O_WRONLY | os.O_NOFOLLOW, 0o600)
7678
+ lock_fd = os.open(lock_path, os.O_CREAT | os.O_WRONLY | getattr(os, "O_NOFOLLOW", 0), 0o600)
7663
7679
  with os.fdopen(lock_fd, "w") as lock_handle:
7664
7680
  fcntl.flock(lock_handle, fcntl.LOCK_EX)
7665
7681
  try:
@@ -8038,7 +8054,7 @@ def customize(
8038
8054
  preview=lambda value: {**plan, policy_field: value},
8039
8055
  )
8040
8056
  elif action == "global-instructions":
8041
- exclude_available = private_corpus_enabled() and plan["host"] == "claude"
8057
+ exclude_available = private_instructions_enabled() and plan["host"] == "claude"
8042
8058
  exclude_reason = (
8043
8059
  t("global-instructions.exclude.codex-unavailable")
8044
8060
  if plan["host"] == "codex"
@@ -8203,7 +8219,7 @@ def customize(
8203
8219
  )
8204
8220
  save_preset(plan, config, config_path, name)
8205
8221
  except LaunchError as exc:
8206
- _corpus_info(ui, t("custom.save.prompt"), [str(exc)],
8222
+ _instructions_info(ui, t("custom.save.prompt"), [str(exc)],
8207
8223
  back_hint=t("custom.title"))
8208
8224
  continue
8209
8225
  plan["_launch_confirmed"] = True
@@ -8213,9 +8229,9 @@ def customize(
8213
8229
 
8214
8230
 
8215
8231
  DISTILL_PRESET = "session-distill"
8216
- CORPUS_STATUS_PATH = pathlib.Path(
8217
- os.environ.get(
8218
- "AGENT_BIOS_CORPUS_STATUS",
8232
+ INSTRUCTIONS_STATUS_PATH = pathlib.Path(
8233
+ _instructions_environment(
8234
+ "AGENT_BIOS_INSTRUCTIONS_STATUS", "AGENT_BIOS_CORPUS_STATUS",
8219
8235
  str(pathlib.Path.home() / ".local/share/agent-bios/corpus-status.json"),
8220
8236
  )
8221
8237
  )
@@ -8238,7 +8254,7 @@ UPDATE_CHECK_PATH = pathlib.Path(
8238
8254
  # at most SPAWNS the installer, detached, to refresh it. Two reasons, both load-bearing:
8239
8255
  # `npm view` routinely takes seconds and a launcher that blocks on the network is worse
8240
8256
  # than one that shows a stale badge, and the network operation belongs to the component
8241
- # that already owns fetch-corpus (ENDPOINTS.md) rather than to the UI.
8257
+ # that already owns fetch-instructions (ENDPOINTS.md) rather than to the UI.
8242
8258
  UPDATE_CHECK_INTERVAL_S = 24 * 60 * 60
8243
8259
 
8244
8260
 
@@ -8316,24 +8332,24 @@ def spawn_update_check() -> None:
8316
8332
  pass
8317
8333
 
8318
8334
 
8319
- def load_corpus_status() -> dict[str, Any] | None:
8320
- """The corpus status projection, or None when the file is unreadable or is not
8335
+ def load_instructions_status() -> dict[str, Any] | None:
8336
+ """The instructions status projection, or None when the file is unreadable or is not
8321
8337
  an object at all. Nothing deeper is validated here on purpose.
8322
8338
 
8323
8339
  Validating the shape field by field was tried and is a queue that refills:
8324
8340
  guarding `summary` and `versions` still left `summary.placed_by_layer: null`,
8325
8341
  `domains.applied: [1,2]` and `last_apply.requested: 5` killing the ROOT MENU
8326
8342
  before it drew, and each new consumer would add another field to remember. The
8327
- depth is unbounded, so the readers DEGRADE instead — see corpus_summary_lines.
8343
+ depth is unbounded, so the readers DEGRADE instead — see instructions_summary_lines.
8328
8344
  """
8329
- if private_corpus_enabled():
8345
+ if private_instructions_enabled():
8330
8346
  try:
8331
- return {"private": corpus_store().status()}
8347
+ return {"private": instructions_store().status()}
8332
8348
  except (OSError, ValueError, RuntimeError) as exc:
8333
- print(f"agent-launch: cannot read private corpus: {exc}", file=sys.stderr)
8349
+ print(f"agent-launch: cannot read private instructions: {exc}", file=sys.stderr)
8334
8350
  return None
8335
8351
  try:
8336
- data = json.loads(CORPUS_STATUS_PATH.read_text(encoding="utf-8"))
8352
+ data = json.loads(INSTRUCTIONS_STATUS_PATH.read_text(encoding="utf-8"))
8337
8353
  except (OSError, ValueError):
8338
8354
  return None
8339
8355
  return data if isinstance(data, dict) else None
@@ -8343,7 +8359,7 @@ def version_label() -> str | None:
8343
8359
  """The deployed agent-bios version + release date for the TUI, or None when
8344
8360
  the marker is absent (uninstalled / dev checkout). `agent-bios install`
8345
8361
  writes it from package.json (version + releaseDate). This is the deploy /
8346
- system version — distinct from the corpus content version in the distill hub."""
8362
+ system version — distinct from the instructions content version in the distill hub."""
8347
8363
  try:
8348
8364
  info = json.loads(VERSION_INFO_PATH.read_text(encoding="utf-8"))
8349
8365
  except (OSError, ValueError):
@@ -8377,7 +8393,7 @@ def display_width(text: str) -> int:
8377
8393
  )
8378
8394
 
8379
8395
 
8380
- def corpus_summary_lines(status: dict[str, Any] | None) -> list[str]:
8396
+ def instructions_summary_lines(status: dict[str, Any] | None) -> list[str]:
8381
8397
  """Panel body for the Session Distill area, or the not-projected line when the
8382
8398
  status cannot be rendered.
8383
8399
 
@@ -8389,10 +8405,10 @@ def corpus_summary_lines(status: dict[str, Any] | None) -> list[str]:
8389
8405
  The exception is printed, never swallowed, so a defect in the rendering code
8390
8406
  below stays visible instead of hiding behind a degraded panel."""
8391
8407
  try:
8392
- return _corpus_summary_lines(status)
8408
+ return _instructions_summary_lines(status)
8393
8409
  except Exception as exc: # noqa: BLE001 — the root menu must survive any shape
8394
8410
  print(
8395
- f"agent-launch: cannot render the corpus status from {CORPUS_STATUS_PATH} "
8411
+ f"agent-launch: cannot render the instructions status from {INSTRUCTIONS_STATUS_PATH} "
8396
8412
  f"({type(exc).__name__}: {exc}); showing it as not projected. "
8397
8413
  "Run: agent-bios install", file=sys.stderr,
8398
8414
  )
@@ -8414,7 +8430,7 @@ def status_list(value: Any) -> list:
8414
8430
  return value if isinstance(value, list) else []
8415
8431
 
8416
8432
 
8417
- _CORPUS_FACTS: dict[tuple, dict[str, Any] | None] = {}
8433
+ _INSTRUCTIONS_FACTS: dict[tuple, dict[str, Any] | None] = {}
8418
8434
 
8419
8435
 
8420
8436
  def size_label(chars: int) -> str:
@@ -8424,7 +8440,7 @@ def size_label(chars: int) -> str:
8424
8440
  return f"{chars / 1024:.1f} KB"
8425
8441
 
8426
8442
 
8427
- def corpus_facts(status: dict[str, Any] | None) -> dict[str, Any] | None:
8443
+ def instructions_facts(status: dict[str, Any] | None) -> dict[str, Any] | None:
8428
8444
  """What each domain package holds and what it costs, or None when the package the
8429
8445
  projection points at cannot be read.
8430
8446
 
@@ -8453,20 +8469,20 @@ def corpus_facts(status: dict[str, Any] | None) -> dict[str, Any] | None:
8453
8469
  stamp = (repo, manifest_path.stat().st_mtime, applied)
8454
8470
  except OSError:
8455
8471
  return None
8456
- if stamp in _CORPUS_FACTS:
8457
- return _CORPUS_FACTS[stamp]
8458
- _CORPUS_FACTS[stamp] = None # a repeat of a failing read must not repeat the cost
8472
+ if stamp in _INSTRUCTIONS_FACTS:
8473
+ return _INSTRUCTIONS_FACTS[stamp]
8474
+ _INSTRUCTIONS_FACTS[stamp] = None # a repeat of a failing read must not repeat the cost
8459
8475
  try:
8460
- facts = _corpus_facts(pathlib.Path(repo), manifest_path, set(applied))
8476
+ facts = _instructions_facts(pathlib.Path(repo), manifest_path, set(applied))
8461
8477
  except SystemExit:
8462
8478
  # assemble.die() on a manifest/monolith disagreement. Not an Exception, so it
8463
8479
  # would otherwise leave the launcher through every absorber in this file.
8464
8480
  return None
8465
8481
  except Exception as exc: # noqa: BLE001 — sizing is decoration; picking is not
8466
- print(f"agent-launch: cannot size the corpus packages in {repo} "
8482
+ print(f"agent-launch: cannot size the instructions packages in {repo} "
8467
8483
  f"({type(exc).__name__}: {exc})", file=sys.stderr)
8468
8484
  return None
8469
- _CORPUS_FACTS[stamp] = facts
8485
+ _INSTRUCTIONS_FACTS[stamp] = facts
8470
8486
  return facts
8471
8487
 
8472
8488
 
@@ -8490,7 +8506,7 @@ def _load_assembler(repo: pathlib.Path):
8490
8506
  return module
8491
8507
 
8492
8508
 
8493
- def _corpus_facts(repo: pathlib.Path, manifest_path: pathlib.Path, applied: set) -> dict[str, Any]:
8509
+ def _instructions_facts(repo: pathlib.Path, manifest_path: pathlib.Path, applied: set) -> dict[str, Any]:
8494
8510
  """The derivation proper, with every failure left to the caller to absorb."""
8495
8511
  assemble = _load_assembler(repo)
8496
8512
  manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
@@ -8539,14 +8555,14 @@ def _corpus_facts(repo: pathlib.Path, manifest_path: pathlib.Path, applied: set)
8539
8555
  "guides": len(selected_guides),
8540
8556
  "guide_chars": guide_chars(selected_guides),
8541
8557
  # A projection can name a domain the installed package does not carry;
8542
- # the figures above then describe a smaller corpus than the row above them
8558
+ # the figures above then describe a smaller instructions than the row above them
8543
8559
  # claims, so the gap is reported rather than folded in.
8544
8560
  "unknown": sorted(applied - set(domains)),
8545
8561
  },
8546
8562
  }
8547
8563
 
8548
8564
 
8549
- def _corpus_summary_lines(status: dict[str, Any] | None) -> list[str]:
8565
+ def _instructions_summary_lines(status: dict[str, Any] | None) -> list[str]:
8550
8566
  """The panel body proper. The label column is derived at render time from the
8551
8567
  widest label in the ACTIVE language and measured in display cells: a pad written
8552
8568
  for English cannot align a translated panel."""
@@ -8555,20 +8571,20 @@ def _corpus_summary_lines(status: dict[str, Any] | None) -> list[str]:
8555
8571
  if isinstance(status.get("private"), dict):
8556
8572
  current = status["private"]
8557
8573
  selection = current.get("selection")
8558
- selected = ", ".join(selection) if selection else t("corpus.launch.base")
8559
- return [t("corpus.private.relationship"),
8574
+ selected = ", ".join(selection) if selection else t("instructions.launch.base")
8575
+ return [t("instructions.private.relationship"),
8560
8576
  f"{t('panel.domains.label')} {selected}",
8561
8577
  f"{t('panel.version.label')} {current.get('selected_baseline_ref') or '—'}"]
8562
8578
  # `versions is None` is the projection saying "this install cannot know" — a packaged
8563
- # install has no author-side registry (compose/corpus-state.py). Three rows then read
8564
- # "unavailable", which is true of the ledger and says nothing about the corpus the
8579
+ # install has no author-side registry (compose/instructions-state.py). Three rows then read
8580
+ # "unavailable", which is true of the ledger and says nothing about the instructions the
8565
8581
  # user actually has. The package carries the manifest and the monolith the installer
8566
8582
  # assembled from, so those rows are answerable from it; the absence is still stated,
8567
8583
  # once, on the row it belongs to.
8568
8584
  unavailable = status.get("versions") is None
8569
8585
  domains = status.get("domains")
8570
8586
  applied_raw = domains.get("applied") if isinstance(domains, dict) else None
8571
- facts = corpus_facts(status)
8587
+ facts = instructions_facts(status)
8572
8588
  # An unapplied install is NOT sized: what is deployed is whatever the install put
8573
8589
  # there, and the manifest cannot say which. A guess here would read as a reading.
8574
8590
  packaged = unavailable and facts is not None and applied_raw is not None
@@ -8595,10 +8611,10 @@ def _corpus_summary_lines(status: dict[str, Any] | None) -> list[str]:
8595
8611
  # rows above describe less than the domain row below them claims.
8596
8612
  lines.append(t("panel.domains.unknown").format(
8597
8613
  names=", ".join(selected["unknown"])))
8598
- return lines + _corpus_domain_lines(status, applied_raw, row, labels[3])
8614
+ return lines + _instructions_domain_lines(status, applied_raw, row, labels[3])
8599
8615
 
8600
8616
  # `versions is None` is the projection saying "this install cannot know" — a packaged
8601
- # install has no author version/ledger registry (compose/corpus-state.py). It is not
8617
+ # install has no author version/ledger registry (compose/instructions-state.py). It is not
8602
8618
  # `[]`, which would mean a checkout whose registry is genuinely empty, and it must not
8603
8619
  # render as `0 placed · 0 versions`: a fabricated zero is worse than a blank, because
8604
8620
  # a reader cannot tell it from a real count. The domain rows below stay real, which is
@@ -8628,10 +8644,10 @@ def _corpus_summary_lines(status: dict[str, Any] | None) -> list[str]:
8628
8644
  versions=len(status_list(status.get("versions"))),
8629
8645
  )),
8630
8646
  ]
8631
- return lines + _corpus_domain_lines(status, applied_raw, row, labels[3])
8647
+ return lines + _instructions_domain_lines(status, applied_raw, row, labels[3])
8632
8648
 
8633
8649
 
8634
- def _corpus_domain_lines(status, applied_raw, row, label: str) -> list[str]:
8650
+ def _instructions_domain_lines(status, applied_raw, row, label: str) -> list[str]:
8635
8651
  """The rows both panels end on: what is applied, and a failed apply if there is one.
8636
8652
 
8637
8653
  Shared rather than written twice — a packaged panel that quietly dropped the failed
@@ -8640,23 +8656,23 @@ def _corpus_domain_lines(status, applied_raw, row, label: str) -> list[str]:
8640
8656
  if isinstance(status.get("domains"), dict):
8641
8657
  applied = None if applied_raw is None else status_list(applied_raw)
8642
8658
  lines.append(row(label, t("panel.domains.unset") if applied is None
8643
- else ", ".join(applied) or t("corpus.core-only")))
8659
+ else ", ".join(applied) or t("instructions.core-only")))
8644
8660
  last_apply = status.get("last_apply")
8645
8661
  if isinstance(last_apply, dict) and last_apply.get("outcome") not in (None, "applied"):
8646
8662
  # A failed apply must be loud on the panel, not a fact buried in a log:
8647
- # the deployed corpus and the requested selection disagree right now.
8663
+ # the deployed instructions and the requested selection disagree right now.
8648
8664
  lines.append(t("panel.last-apply").format(
8649
8665
  outcome=last_apply.get("outcome"),
8650
8666
  at=last_apply.get("at"),
8651
- requested=", ".join(status_list(last_apply.get("requested"))) or t("corpus.core-only"),
8667
+ requested=", ".join(status_list(last_apply.get("requested"))) or t("instructions.core-only"),
8652
8668
  ))
8653
8669
  return lines
8654
8670
 
8655
8671
 
8656
- def _corpus_screen(ui: TextualUI | None, screen: "Callable[[], None]") -> None:
8657
- """Run a corpus screen, absorbing a status shape it cannot render.
8672
+ def _instructions_screen(ui: TextualUI | None, screen: "Callable[[], None]") -> None:
8673
+ """Run an instruction screen, absorbing a status shape it cannot render.
8658
8674
 
8659
- `corpus_summary_lines` argued this for the root panel: the projection's depth is
8675
+ `instructions_summary_lines` argued this for the root panel: the projection's depth is
8660
8676
  unbounded, its writer is not the only thing that can produce the file, so a status
8661
8677
  that cannot be rendered is treated as one that is not projected. That argument was
8662
8678
  implemented for the panel alone, and the three screens one keypress deeper read the
@@ -8666,25 +8682,25 @@ def _corpus_screen(ui: TextualUI | None, screen: "Callable[[], None]") -> None:
8666
8682
  the top and fatal underneath.
8667
8683
 
8668
8684
  Control flow passes through untouched: LaunchError is the launcher's own refusal,
8669
- BackRequested and CorpusApplyRequested are how these screens exit normally, and
8685
+ BackRequested and InstructionsApplyRequested are how these screens exit normally, and
8670
8686
  KeyboardInterrupt/SystemExit are not Exception. The rest is printed and shown rather
8671
8687
  than swallowed, so a defect in the rendering code stays visible.
8672
8688
  """
8673
8689
  try:
8674
8690
  screen()
8675
- except (LaunchError, BackRequested, CorpusApplyRequested):
8691
+ except (LaunchError, BackRequested, InstructionsApplyRequested):
8676
8692
  raise
8677
8693
  except Exception as exc: # noqa: BLE001 — a status shape must not end the session
8678
8694
  print(
8679
- f"agent-launch: cannot render the corpus status from {CORPUS_STATUS_PATH} "
8695
+ f"agent-launch: cannot render the instructions status from {INSTRUCTIONS_STATUS_PATH} "
8680
8696
  f"({type(exc).__name__}: {exc}); showing it as not projected. "
8681
8697
  "Run: agent-bios install",
8682
8698
  file=sys.stderr,
8683
8699
  )
8684
- _corpus_info(ui, t("distill.title"), [t("panel.unprojected")])
8700
+ _instructions_info(ui, t("distill.title"), [t("panel.unprojected")])
8685
8701
 
8686
8702
 
8687
- def _corpus_info(
8703
+ def _instructions_info(
8688
8704
  ui: TextualUI | None, title: str, lines: list[str],
8689
8705
  back_hint: str | None = None,
8690
8706
  ) -> None:
@@ -8702,12 +8718,12 @@ def _corpus_info(
8702
8718
  )
8703
8719
  ]
8704
8720
  try:
8705
- choose(title, options, "back", ui, allow_back=True, corpus_lines=lines)
8721
+ choose(title, options, "back", ui, allow_back=True, instructions_lines=lines)
8706
8722
  except BackRequested:
8707
8723
  pass
8708
8724
 
8709
8725
 
8710
- def _corpus_rollback_choice(version: str, ui: "TextualUI | None") -> str:
8726
+ def _instructions_rollback_choice(version: str, ui: "TextualUI | None") -> str:
8711
8727
  """The rollback confirmation prompt, so the Esc handling wraps exactly it."""
8712
8728
  return choose(
8713
8729
  t("distill.rollback.title").format(version=version),
@@ -8729,13 +8745,13 @@ def _corpus_rollback_choice(version: str, ui: "TextualUI | None") -> str:
8729
8745
  )
8730
8746
 
8731
8747
 
8732
- def _corpus_rollback(status: dict[str, Any], version: str, ui: TextualUI | None) -> None:
8748
+ def _instructions_rollback(status: dict[str, Any], version: str, ui: TextualUI | None) -> None:
8733
8749
  # Esc means the same thing the Cancel entry means, and this screen offered both while
8734
8750
  # honouring only one: BackRequested propagated out of a destructive confirmation as an
8735
8751
  # exception, past every caller that had nothing to do with it. A screen that shows a
8736
8752
  # Cancel option and a back hint has already told the user those are the same answer.
8737
8753
  try:
8738
- confirm = _corpus_rollback_choice(version, ui)
8754
+ confirm = _instructions_rollback_choice(version, ui)
8739
8755
  except BackRequested:
8740
8756
  return
8741
8757
  if confirm != "rollback":
@@ -8746,10 +8762,10 @@ def _corpus_rollback(status: dict[str, Any], version: str, ui: TextualUI | None)
8746
8762
  # an outcome this screen already knows how to report.
8747
8763
  repo = status.get("repo")
8748
8764
  if not isinstance(repo, str) or not repo:
8749
- _corpus_info(ui, t("distill.rollback.failed"),
8765
+ _instructions_info(ui, t("distill.rollback.failed"),
8750
8766
  [t("panel.unprojected")], back_hint=t("distill.back.hint"))
8751
8767
  return
8752
- script = pathlib.Path(repo) / "compose/corpus-state.py"
8768
+ script = pathlib.Path(repo) / "compose/instructions-state.py"
8753
8769
  result = subprocess.run(
8754
8770
  [sys.executable, str(script), "rollback", "--version", version],
8755
8771
  capture_output=True,
@@ -8761,14 +8777,14 @@ def _corpus_rollback(status: dict[str, Any], version: str, ui: TextualUI | None)
8761
8777
  t("distill.rollback.done") if result.returncode == 0
8762
8778
  else t("distill.rollback.failed")
8763
8779
  )
8764
- _corpus_info(ui, verdict, [tail] if tail else [])
8780
+ _instructions_info(ui, verdict, [tail] if tail else [])
8765
8781
 
8766
8782
 
8767
- def _corpus_versions(ui: TextualUI | None) -> None:
8783
+ def _instructions_versions(ui: TextualUI | None) -> None:
8768
8784
  while True:
8769
- status = load_corpus_status()
8785
+ status = load_instructions_status()
8770
8786
  if status is None:
8771
- _corpus_info(ui, t("distill.versions.label"), corpus_summary_lines(None))
8787
+ _instructions_info(ui, t("distill.versions.label"), instructions_summary_lines(None))
8772
8788
  return
8773
8789
  current = status.get("current_version")
8774
8790
  options = []
@@ -8809,28 +8825,28 @@ def _corpus_versions(ui: TextualUI | None) -> None:
8809
8825
  options[0].value,
8810
8826
  ui,
8811
8827
  allow_back=True,
8812
- corpus_lines=corpus_summary_lines(status),
8828
+ instructions_lines=instructions_summary_lines(status),
8813
8829
  )
8814
8830
  except BackRequested:
8815
8831
  return
8816
8832
  if selected == "back":
8817
8833
  return
8818
8834
  if selected == current:
8819
- _corpus_info(
8835
+ _instructions_info(
8820
8836
  ui,
8821
8837
  t("distill.version.applied.title").format(version=selected),
8822
8838
  [t("distill.version.applied.line")],
8823
8839
  )
8824
8840
  continue
8825
- _corpus_rollback(status, selected, ui)
8841
+ _instructions_rollback(status, selected, ui)
8826
8842
 
8827
8843
 
8828
- def _corpus_packages(ui: TextualUI | None) -> None:
8829
- """v1: the corpus ships as a single core package; the list shape is ready
8844
+ def _instructions_packages(ui: TextualUI | None) -> None:
8845
+ """v1: the instructions ships as a single core package; the list shape is ready
8830
8846
  for the domain-packaging backlog to populate with real packages."""
8831
- status = load_corpus_status()
8847
+ status = load_instructions_status()
8832
8848
  if status is None:
8833
- _corpus_info(ui, t("distill.packages.label"), corpus_summary_lines(None))
8849
+ _instructions_info(ui, t("distill.packages.label"), instructions_summary_lines(None))
8834
8850
  return
8835
8851
  layers = status.get("summary", {}).get("placed_by_layer", {})
8836
8852
  options = [
@@ -8852,7 +8868,7 @@ def _corpus_packages(ui: TextualUI | None) -> None:
8852
8868
  "back",
8853
8869
  ui,
8854
8870
  allow_back=True,
8855
- corpus_lines=corpus_summary_lines(status),
8871
+ instructions_lines=instructions_summary_lines(status),
8856
8872
  )
8857
8873
  except BackRequested:
8858
8874
  pass
@@ -8864,7 +8880,7 @@ def distill_hub(config: dict[str, Any], ui: TextualUI | None) -> str:
8864
8880
  Returns "start" to launch the session-distill preset, "back" otherwise.
8865
8881
  """
8866
8882
  while True:
8867
- status = load_corpus_status()
8883
+ status = load_instructions_status()
8868
8884
  options = [
8869
8885
  MenuOption(
8870
8886
  "start",
@@ -8894,7 +8910,7 @@ def distill_hub(config: dict[str, Any], ui: TextualUI | None) -> str:
8894
8910
  "start",
8895
8911
  ui,
8896
8912
  allow_back=True,
8897
- corpus_lines=corpus_summary_lines(status),
8913
+ instructions_lines=instructions_summary_lines(status),
8898
8914
  )
8899
8915
  except BackRequested:
8900
8916
  return "back"
@@ -8903,9 +8919,9 @@ def distill_hub(config: dict[str, Any], ui: TextualUI | None) -> str:
8903
8919
  if selected == "start":
8904
8920
  return "start"
8905
8921
  if selected == "packages":
8906
- _corpus_screen(ui, lambda: _corpus_packages(ui))
8922
+ _instructions_screen(ui, lambda: _instructions_packages(ui))
8907
8923
  elif selected == "versions":
8908
- _corpus_screen(ui, lambda: _corpus_versions(ui))
8924
+ _instructions_screen(ui, lambda: _instructions_versions(ui))
8909
8925
 
8910
8926
 
8911
8927
  def preset_mode(data: dict[str, Any]) -> str:
@@ -8927,8 +8943,8 @@ def mode_default_preset(presets: dict[str, Any], mode: str) -> str | None:
8927
8943
  return names[0] if names else None
8928
8944
 
8929
8945
 
8930
- class CorpusApplyRequested(Exception):
8931
- """Raised out of the interactive flow when the user confirms a corpus
8946
+ class InstructionsApplyRequested(Exception):
8947
+ """Raised out of the interactive flow when the user confirms an instruction
8932
8948
  selection: the Textual app must be torn down before the installer owns the
8933
8949
  terminal, so the request travels as control flow and main() runs the apply
8934
8950
  in the plain terminal, then re-enters the picker."""
@@ -8938,22 +8954,22 @@ class CorpusApplyRequested(Exception):
8938
8954
  self.selection = selection
8939
8955
 
8940
8956
 
8941
- class CorpusStudioRequested(Exception):
8957
+ class InstructionsStudioRequested(Exception):
8942
8958
  """Release Textual's terminal before starting the standalone manager."""
8943
8959
 
8944
8960
 
8945
- CORPUS_OPTION = "__corpus__"
8961
+ INSTRUCTIONS_OPTION = "__instructions__"
8946
8962
  # A plain value, because the numbered prompt prints it as the default label.
8947
8963
  # Collision with a domain id is structurally impossible: domain slugs come from
8948
8964
  # compose/domains.json, which this repo owns and which carries no "apply".
8949
- CORPUS_APPLY = "apply"
8965
+ INSTRUCTIONS_APPLY = "apply"
8950
8966
 
8951
8967
 
8952
- def run_corpus_apply(selection: list[str]) -> None:
8968
+ def run_instructions_apply(selection: list[str]) -> None:
8953
8969
  """Run the installer's onboard loop for the selection, in the caller's
8954
8970
  terminal. The installer alone deploys, canaries, and records the outcome;
8955
8971
  this function only streams it and reports the exit."""
8956
- status = load_corpus_status() or {}
8972
+ status = load_instructions_status() or {}
8957
8973
  domains = ",".join(selection) if selection else "none"
8958
8974
  interaction = [] if os.environ.get("AGENT_BIOS_LEGACY_INSTALL") == "1" else ["--non-interactive"]
8959
8975
  front = shutil.which("agent-bios")
@@ -8976,15 +8992,15 @@ def run_corpus_apply(selection: list[str]) -> None:
8976
8992
  )
8977
8993
  return
8978
8994
  argv = ["bash", str(installer), "onboard", *interaction, "--domains", domains]
8979
- print(f"\nagent-launch: applying corpus selection: {' '.join(argv)}\n", flush=True)
8995
+ print(f"\nagent-launch: applying instructions selection: {' '.join(argv)}\n", flush=True)
8980
8996
  proc = subprocess.run(argv)
8981
8997
  if proc.returncode != 0:
8982
8998
  print(
8983
- f"\nagent-launch: corpus apply FAILED (exit {proc.returncode}); the "
8984
- "corpus panel shows the recorded outcome.", file=sys.stderr,
8999
+ f"\nagent-launch: instructions apply FAILED (exit {proc.returncode}); the "
9000
+ "instructions panel shows the recorded outcome.", file=sys.stderr,
8985
9001
  )
8986
9002
  else:
8987
- print("\nagent-launch: corpus selection applied.", flush=True)
9003
+ print("\nagent-launch: instructions selection applied.", flush=True)
8988
9004
 
8989
9005
 
8990
9006
  def checkbox_label(checked: bool, name: str, trailing: str = ""):
@@ -9002,25 +9018,25 @@ def checkbox_label(checked: bool, name: str, trailing: str = ""):
9002
9018
  return Text.assemble(*box, name, (trailing, "dim"))
9003
9019
 
9004
9020
 
9005
- def _corpus_domain_content(name: str, fallback: str = "") -> str:
9021
+ def _instructions_domain_content(name: str, fallback: str = "") -> str:
9006
9022
  """Localized content help; unfamiliar packages retain their manifest description."""
9007
9023
  descriptions = {
9008
- "builder-base": t("corpus.domain.builder-base"),
9009
- "llm-pipeline-dev": t("corpus.domain.llm-pipeline-dev"),
9010
- "multi-agent-orchestration": t("corpus.domain.multi-agent-orchestration"),
9011
- "visualization-docs": t("corpus.domain.visualization-docs"),
9012
- "office-work": t("corpus.domain.office-work"),
9024
+ "builder-base": t("instructions.domain.builder-base"),
9025
+ "llm-pipeline-dev": t("instructions.domain.llm-pipeline-dev"),
9026
+ "multi-agent-orchestration": t("instructions.domain.multi-agent-orchestration"),
9027
+ "visualization-docs": t("instructions.domain.visualization-docs"),
9028
+ "office-work": t("instructions.domain.office-work"),
9013
9029
  }
9014
9030
  return descriptions.get(name, fallback).strip()
9015
9031
 
9016
9032
 
9017
- def corpus_domain_labels() -> dict[str, str]:
9033
+ def instructions_domain_labels() -> dict[str, str]:
9018
9034
  return {
9019
- "builder-base": t("corpus.domain.builder-base.label"),
9020
- "llm-pipeline-dev": t("corpus.domain.llm-pipeline-dev.label"),
9021
- "multi-agent-orchestration": t("corpus.domain.multi-agent-orchestration.label"),
9022
- "visualization-docs": t("corpus.domain.visualization-docs.label"),
9023
- "office-work": t("corpus.domain.office-work.label"),
9035
+ "builder-base": t("instructions.domain.builder-base.label"),
9036
+ "llm-pipeline-dev": t("instructions.domain.llm-pipeline-dev.label"),
9037
+ "multi-agent-orchestration": t("instructions.domain.multi-agent-orchestration.label"),
9038
+ "visualization-docs": t("instructions.domain.visualization-docs.label"),
9039
+ "office-work": t("instructions.domain.office-work.label"),
9024
9040
  }
9025
9041
 
9026
9042
 
@@ -9031,16 +9047,16 @@ def preset_descriptions() -> dict[str, str]:
9031
9047
  "deep-review": t("preset.deep-review.description"),
9032
9048
  "fast-batch": t("preset.fast-batch.description"),
9033
9049
  "solo": t("preset.solo.description"),
9034
- "vanilla": (t("corpus.private.vanilla") if private_corpus_enabled()
9050
+ "vanilla": (t("instructions.private.vanilla") if private_instructions_enabled()
9035
9051
  else t("preset.vanilla.description")),
9036
9052
  }
9037
9053
 
9038
9054
 
9039
- def _corpus_launch_description(status: dict[str, Any] | None) -> str:
9055
+ def _instructions_launch_description(status: dict[str, Any] | None) -> str:
9040
9056
  """Explain the installed selection shared by modes without claiming a launch applies it."""
9041
- if private_corpus_enabled():
9042
- return t("corpus.private.relationship")
9043
- lines = [t("corpus.launch.relationship")]
9057
+ if private_instructions_enabled():
9058
+ return t("instructions.private.relationship")
9059
+ lines = [t("instructions.launch.relationship")]
9044
9060
  domains = (status or {}).get("domains")
9045
9061
  applied = domains.get("applied") if isinstance(domains, dict) else None
9046
9062
  if not isinstance(applied, list) or not all(isinstance(name, str) for name in applied):
@@ -9048,12 +9064,12 @@ def _corpus_launch_description(status: dict[str, Any] | None) -> str:
9048
9064
  if isinstance(domains, dict) and "applied" in domains and applied is None
9049
9065
  else t("panel.unprojected"))
9050
9066
  return "\n".join([*lines, message])
9051
- lines += [t("corpus.launch.applied"), t("corpus.launch.base")]
9052
- facts = (corpus_facts(status) or {}).get("domains", {})
9053
- labels = corpus_domain_labels()
9067
+ lines += [t("instructions.launch.applied"), t("instructions.launch.base")]
9068
+ facts = (instructions_facts(status) or {}).get("domains", {})
9069
+ labels = instructions_domain_labels()
9054
9070
  for name in applied:
9055
- content = _corpus_domain_content(
9056
- name, facts.get(name, {}).get("description") or t("corpus.launch.unknown"),
9071
+ content = _instructions_domain_content(
9072
+ name, facts.get(name, {}).get("description") or t("instructions.launch.unknown"),
9057
9073
  )
9058
9074
  lines.append(f"• {labels.get(name, name)}: {content.splitlines()[0]}")
9059
9075
  return "\n".join(lines)
@@ -9068,53 +9084,53 @@ def _domain_description(info: dict[str, Any] | None, name: str = "") -> str:
9068
9084
  and they are kept apart because they are spent differently: rules are in the global
9069
9085
  that every session and every subagent loads, guides are on disk and read only when a
9070
9086
  rule points at one."""
9071
- content = _corpus_domain_content(name, info.get("description", "") if info else "")
9087
+ content = _instructions_domain_content(name, info.get("description", "") if info else "")
9072
9088
  if info is None:
9073
- return "\n\n".join(filter(None, [content, t("corpus.toggle.description")]))
9089
+ return "\n\n".join(filter(None, [content, t("instructions.toggle.description")]))
9074
9090
  # Both keys are named at a literal call site rather than chosen into a variable:
9075
9091
  # the catalog gate finds keys by reading the quoted argument of each t() call, so a
9076
9092
  # computed key is a string no language is ever checked for.
9077
9093
  if info["guides"]:
9078
- sizes = t("corpus.size.detail").format(
9094
+ sizes = t("instructions.size.detail").format(
9079
9095
  rules=info["rules"],
9080
9096
  size=size_label(info["chars"]),
9081
9097
  guides=info["guides"],
9082
9098
  guide_size=size_label(info["guide_chars"]),
9083
9099
  )
9084
9100
  else:
9085
- sizes = t("corpus.size.detail.noguides").format(
9101
+ sizes = t("instructions.size.detail.noguides").format(
9086
9102
  rules=info["rules"], size=size_label(info["chars"]),
9087
9103
  )
9088
- return "\n\n".join(filter(None, [content, sizes, t("corpus.toggle.description")]))
9104
+ return "\n\n".join(filter(None, [content, sizes, t("instructions.toggle.description")]))
9089
9105
 
9090
9106
 
9091
- def corpus_checklist(ui: "TextualUI | None") -> None:
9107
+ def instructions_checklist(ui: "TextualUI | None") -> None:
9092
9108
  """Toggle-and-apply loop over the optional domain packages.
9093
9109
 
9094
9110
  The list and the applied set come from corpus-status.json — the installer's
9095
9111
  projection — never from repo paths this launcher cannot know. Selection state
9096
9112
  lives only on this screen; Apply hands the exact set to the installer
9097
9113
  (raising through the Textual app so the terminal is free), and Esc leaves the
9098
- deployed corpus untouched."""
9099
- status = load_corpus_status()
9114
+ deployed instructions untouched."""
9115
+ status = load_instructions_status()
9100
9116
  domains = (status or {}).get("domains")
9101
9117
  if not isinstance(domains, dict) or not isinstance(domains.get("available"), list):
9102
- _corpus_info(
9103
- ui, t("corpus.title"),
9104
- [t("corpus.unavailable.line1"), t("corpus.unavailable.line2")],
9105
- back_hint=t("corpus.back.hint"),
9118
+ _instructions_info(
9119
+ ui, t("instructions.title"),
9120
+ [t("instructions.unavailable.line1"), t("instructions.unavailable.line2")],
9121
+ back_hint=t("instructions.back.hint"),
9106
9122
  )
9107
9123
  return
9108
9124
  available = domains["available"]
9109
9125
  if not available:
9110
9126
  # A list that is empty passes the shape check above and then builds a menu whose
9111
9127
  # only row is a disabled Apply, which `choose` refuses as "no available options"
9112
- # — a LaunchError, which `_corpus_screen` rethrows by design, so the session ends
9128
+ # — a LaunchError, which `_instructions_screen` rethrows by design, so the session ends
9113
9129
  # on a projection that is not even malformed. Told, not raised. Its own message
9114
9130
  # rather than the one above: "run the installer" is wrong advice for a projection
9115
9131
  # that read fine and simply lists nothing optional.
9116
- _corpus_info(ui, t("corpus.title"), [t("corpus.none.line")],
9117
- back_hint=t("corpus.back.hint"))
9132
+ _instructions_info(ui, t("instructions.title"), [t("instructions.none.line")],
9133
+ back_hint=t("instructions.back.hint"))
9118
9134
  return
9119
9135
  applied_raw = domains.get("applied")
9120
9136
  # None is "never selected", [] is a verified core+infra-only selection —
@@ -9127,9 +9143,9 @@ def corpus_checklist(ui: "TextualUI | None") -> None:
9127
9143
  # read, and every use below falls back to the bare name — a checklist that cannot
9128
9144
  # size its packages is still a checklist, and this screen is how a user reaches the
9129
9145
  # installer that would repair the projection.
9130
- facts = corpus_facts(status)
9146
+ facts = instructions_facts(status)
9131
9147
  sized = (facts or {}).get("domains", {})
9132
- labels = corpus_domain_labels()
9148
+ labels = instructions_domain_labels()
9133
9149
  display_names = {name: f"{labels[name]} ({name})" if name in labels else name for name in available}
9134
9150
  column = max(display_width(label) for label in display_names.values()) + 2
9135
9151
  while True:
@@ -9147,35 +9163,35 @@ def corpus_checklist(ui: "TextualUI | None") -> None:
9147
9163
  ]
9148
9164
  changed = toggles != applied or never_applied
9149
9165
  pending = "" if not changed else (
9150
- " → " + (", ".join(sorted(toggles)) or t("corpus.core-only"))
9166
+ " → " + (", ".join(sorted(toggles)) or t("instructions.core-only"))
9151
9167
  )
9152
9168
  options.append(
9153
9169
  MenuOption(
9154
- CORPUS_APPLY,
9155
- t("corpus.apply.label").format(pending=pending),
9156
- t("corpus.apply.description"),
9170
+ INSTRUCTIONS_APPLY,
9171
+ t("instructions.apply.label").format(pending=pending),
9172
+ t("instructions.apply.description"),
9157
9173
  enabled=changed,
9158
- unavailable_reason=t("corpus.apply.unchanged"),
9174
+ unavailable_reason=t("instructions.apply.unchanged"),
9159
9175
  )
9160
9176
  )
9161
9177
  try:
9162
9178
  choice = choose(
9163
- t("corpus.title"), options, CORPUS_APPLY, ui, allow_back=True,
9164
- corpus_lines=[
9165
- t("corpus.core.line"),
9166
- *([] if not sized else [t("corpus.size.legend")]),
9167
- *corpus_summary_lines(status),
9179
+ t("instructions.title"), options, INSTRUCTIONS_APPLY, ui, allow_back=True,
9180
+ instructions_lines=[
9181
+ t("instructions.core.line"),
9182
+ *([] if not sized else [t("instructions.size.legend")]),
9183
+ *instructions_summary_lines(status),
9168
9184
  ],
9169
- confirm=CORPUS_APPLY,
9185
+ confirm=INSTRUCTIONS_APPLY,
9170
9186
  )
9171
9187
  except BackRequested:
9172
9188
  return
9173
- if choice == CORPUS_APPLY:
9189
+ if choice == INSTRUCTIONS_APPLY:
9174
9190
  selection = sorted(toggles)
9175
9191
  if ui is None:
9176
- run_corpus_apply(selection)
9192
+ run_instructions_apply(selection)
9177
9193
  return
9178
- raise CorpusApplyRequested(selection)
9194
+ raise InstructionsApplyRequested(selection)
9179
9195
  if choice in toggles:
9180
9196
  toggles.discard(choice)
9181
9197
  else:
@@ -9197,7 +9213,7 @@ def understand_menu(ui: TextualUI | None) -> str | None:
9197
9213
  try:
9198
9214
  bundles = understand_manager().list_bundles()
9199
9215
  if not bundles:
9200
- _corpus_info(ui, t("understand.title"), [t("understand.empty")])
9216
+ _instructions_info(ui, t("understand.title"), [t("understand.empty")])
9201
9217
  return None
9202
9218
  return choose(t("understand.title"), [
9203
9219
  MenuOption(row["id"], row["title"],
@@ -9205,11 +9221,11 @@ def understand_menu(ui: TextualUI | None) -> str | None:
9205
9221
  count=row["item_count"]))
9206
9222
  for row in bundles
9207
9223
  ], bundles[0]["id"], ui, allow_back=True,
9208
- corpus_lines=[t("understand.description"), t("understand.session.scope")])
9224
+ instructions_lines=[t("understand.description"), t("understand.session.scope")])
9209
9225
  except BackRequested:
9210
9226
  return None
9211
9227
  except (OSError, RuntimeError, ValueError) as exc:
9212
- _corpus_info(ui, t("understand.title"), [str(exc)])
9228
+ _instructions_info(ui, t("understand.title"), [str(exc)])
9213
9229
  return None
9214
9230
 
9215
9231
 
@@ -9228,24 +9244,24 @@ def shell_connection_menu(ui: TextualUI | None, dry_run: bool = False) -> None:
9228
9244
  MenuOption("restore", t("shell.restore"), t("shell.restore.description")),
9229
9245
  MenuOption("remove", t("shell.remove"), t("shell.remove.description")),
9230
9246
  MenuOption("back", t("distill.back.label"), t("shell.back")),
9231
- ], "back", ui, allow_back=True, corpus_lines=lines)
9247
+ ], "back", ui, allow_back=True, instructions_lines=lines)
9232
9248
  if selected == "back":
9233
9249
  return
9234
9250
  decision = choose(t("shell.confirm"), [
9235
9251
  MenuOption("cancel", t("shell.cancel"), t("shell.back")),
9236
9252
  MenuOption("apply", t("shell.apply"), t("shell.scope")),
9237
- ], "cancel", ui, allow_back=True, corpus_lines=lines)
9253
+ ], "cancel", ui, allow_back=True, instructions_lines=lines)
9238
9254
  if decision != "apply":
9239
9255
  continue
9240
9256
  result = manager.apply(selected, dry_run=dry_run)
9241
9257
  message = (t("shell.preview") if dry_run else
9242
9258
  t("shell.restored") if selected == "restore" else t("shell.removed"))
9243
- _corpus_info(ui, t("shell.title"), [message, *result["changed_paths"],
9259
+ _instructions_info(ui, t("shell.title"), [message, *result["changed_paths"],
9244
9260
  *result.get("needs_action", [])])
9245
9261
  except BackRequested:
9246
9262
  return
9247
9263
  except (ShellIntegrationError, OSError, RuntimeError) as exc:
9248
- _corpus_info(ui, t("shell.title"), [str(exc)])
9264
+ _instructions_info(ui, t("shell.title"), [str(exc)])
9249
9265
  # Language names render in their own language BY DESIGN — a reader hunting for
9250
9266
  # their language must be able to recognise it whatever UI language is active — so
9251
9267
  # these labels are deliberately catalog-independent.
@@ -9285,7 +9301,7 @@ def choose_language(
9285
9301
  # refusal is reported and returns to the hub; this is that, for the same
9286
9302
  # reason. The catalogs are deliberately not reloaded: the file still holds the
9287
9303
  # previous language, so the session keeps rendering what was actually saved.
9288
- _corpus_info(ui, t("language.title"), [str(exc)])
9304
+ _instructions_info(ui, t("language.title"), [str(exc)])
9289
9305
  return
9290
9306
  load_catalogs(config_path)
9291
9307
 
@@ -9314,31 +9330,31 @@ def pick_mode_and_preset(
9314
9330
  user_names = set(load_user_presets(user_presets_path(config_path))) if config_path else set()
9315
9331
  mode = resume_mode
9316
9332
  while True:
9317
- status = load_corpus_status()
9318
- corpus_description = _corpus_launch_description(status)
9333
+ status = load_instructions_status()
9334
+ instructions_description = _instructions_launch_description(status)
9319
9335
  if mode is None:
9320
9336
  mode_options = [
9321
9337
  MenuOption(SWE_MODE, t("mode.swe.label"),
9322
- t("mode.swe.description") + "\n\n" + corpus_description),
9338
+ t("mode.swe.description") + "\n\n" + instructions_description),
9323
9339
  MenuOption(
9324
9340
  DEFAULT_PRESET_MODE,
9325
9341
  t("mode.builder.label"),
9326
- t("mode.builder.description") + "\n\n" + corpus_description,
9342
+ t("mode.builder.description") + "\n\n" + instructions_description,
9327
9343
  ),
9328
9344
  MenuOption(
9329
9345
  DISTILL_MODE,
9330
9346
  t("mode.distill.label"),
9331
- t("mode.distill.description") + "\n\n" + corpus_description,
9347
+ t("mode.distill.description") + "\n\n" + instructions_description,
9332
9348
  ),
9333
9349
  ]
9334
9350
  mode_options.append(
9335
9351
  MenuOption(
9336
- CORPUS_OPTION,
9337
- t("corpus.private.title") if private_corpus_enabled() else t("corpus.label"),
9338
- t("corpus.private.description") if private_corpus_enabled() else t("corpus.description"),
9352
+ INSTRUCTIONS_OPTION,
9353
+ t("instructions.private.title") if private_instructions_enabled() else t("instructions.label"),
9354
+ t("instructions.private.description") if private_instructions_enabled() else t("instructions.description"),
9339
9355
  )
9340
9356
  )
9341
- if private_corpus_enabled():
9357
+ if private_instructions_enabled():
9342
9358
  mode_options.append(MenuOption(UNDERSTAND_OPTION, t("understand.title"), t("understand.description")))
9343
9359
  mode_options.append(MenuOption(SHELL_CONNECTION_OPTION, t("shell.title"), t("shell.description")))
9344
9360
  if config_path is not None:
@@ -9386,16 +9402,16 @@ def pick_mode_and_preset(
9386
9402
  DEFAULT_PRESET_MODE,
9387
9403
  ui,
9388
9404
  preview=preview_mode,
9389
- corpus_lines=corpus_summary_lines(status),
9405
+ instructions_lines=instructions_summary_lines(status),
9390
9406
  )
9391
- if mode == CORPUS_OPTION:
9392
- if private_corpus_enabled():
9407
+ if mode == INSTRUCTIONS_OPTION:
9408
+ if private_instructions_enabled():
9393
9409
  if ui is not None:
9394
- raise CorpusStudioRequested()
9395
- open_corpus_studio()
9410
+ raise InstructionsStudioRequested()
9411
+ open_instructions_studio()
9396
9412
  mode = None
9397
9413
  continue
9398
- _corpus_screen(ui, lambda: corpus_checklist(ui))
9414
+ _instructions_screen(ui, lambda: instructions_checklist(ui))
9399
9415
  mode = None
9400
9416
  continue
9401
9417
  if mode == SHELL_CONNECTION_OPTION:
@@ -9427,7 +9443,7 @@ def pick_mode_and_preset(
9427
9443
  data["label"],
9428
9444
  (preset_descriptions().get(name, data.get("description", ""))
9429
9445
  if name not in user_names else data.get("description", ""))
9430
- + "\n\n" + corpus_description,
9446
+ + "\n\n" + instructions_description,
9431
9447
  )
9432
9448
  for name, data in presets.items()
9433
9449
  if preset_mode(data) == mode
@@ -9438,7 +9454,7 @@ def pick_mode_and_preset(
9438
9454
  CUSTOM_PRESET,
9439
9455
  t("preset.custom.label"),
9440
9456
  t("preset.custom.description")
9441
- + "\n\n" + corpus_description,
9457
+ + "\n\n" + instructions_description,
9442
9458
  )
9443
9459
  )
9444
9460
  default = mode_default_preset(presets, mode) or CUSTOM_PRESET
@@ -9609,11 +9625,11 @@ def child_agent_registrations(
9609
9625
  raise LaunchError(f"Codex agent template requires description: {source}")
9610
9626
  data["model"] = plan["tiers"][tier]["model"]
9611
9627
  data["model_reasoning_effort"] = tier_effort(plan, tier)
9612
- if plan.get("corpus_instruction_text"):
9613
- # The child's own instruction body and the selected parent corpus are
9628
+ if plan.get("instructions_instruction_text"):
9629
+ # The child's own instruction body and the selected parent instructions are
9614
9630
  # composed before the content-addressed config path is derived.
9615
9631
  existing = data.get("developer_instructions", "")
9616
- data["developer_instructions"] = existing + "\n\n" + plan["corpus_instruction_text"]
9632
+ data["developer_instructions"] = existing + "\n\n" + plan["instructions_instruction_text"]
9617
9633
  lines = []
9618
9634
  for key, value in data.items():
9619
9635
  if not isinstance(value, (str, int, float, bool)):
@@ -10123,7 +10139,7 @@ def delegation_clause(plan: dict[str, Any]) -> str:
10123
10139
 
10124
10140
  The CLI's line carries its own exception, so the fix is to satisfy it rather than argue
10125
10141
  with it. Delegation IS the user's standing request — they chose the preset, and the
10126
- corpus's standing spawn policy is their instruction — so the contract says so in the
10142
+ instructions's standing spawn policy is their instruction — so the contract says so in the
10127
10143
  words that clause is looking for. Nothing is overridden; a fact that was already true
10128
10144
  is simply stated where the reader can see it."""
10129
10145
  if sweep_main(plan):
@@ -10164,8 +10180,8 @@ def claude_agents(plan: dict[str, Any]) -> str:
10164
10180
  # allowlist enforces its one-rule-per-item contract instead of merely
10165
10181
  # restating it in the prompt.
10166
10182
  prompt += " Apply one explicit read-only rule per item; do not make semantic judgments."
10167
- if plan.get("corpus_instruction_text"):
10168
- prompt += "\n\n" + plan["corpus_instruction_text"]
10183
+ if plan.get("instructions_instruction_text"):
10184
+ prompt += "\n\n" + plan["instructions_instruction_text"]
10169
10185
  role = {
10170
10186
  "description": f"{tier.upper()} tier: {format_model_effort(binding['model'], effort)}",
10171
10187
  "prompt": prompt,
@@ -10647,7 +10663,7 @@ def print_summary(
10647
10663
  "(permission mode, not OS sandbox)",
10648
10664
  file=stream,
10649
10665
  )
10650
- if private_corpus_enabled():
10666
+ if private_instructions_enabled():
10651
10667
  print(
10652
10668
  " Global files "
10653
10669
  + t("global-instructions.summary").format(
@@ -10729,16 +10745,16 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
10729
10745
  )
10730
10746
  parser.add_argument("--yes", action="store_true", help="skip launch confirmation")
10731
10747
  parser.add_argument("--dry-run", action="store_true", help="print projection without launching")
10732
- parser.add_argument("--corpus", action="store_true", help="open private Corpus Studio")
10733
- parser.add_argument("--understand", metavar="BUNDLE", help="start an interactive learning session for a corpus bundle; list with agent-bios understand list")
10734
- parser.add_argument("--corpus-domains", help="domain selection for this activated session only")
10735
- parser.add_argument("--corpus-native", action="store_true", help="opt into selected corpus hooks on either host and Claude native agents for this session only")
10748
+ parser.add_argument("--instructions", "--corpus", action="store_true", help="open private Instructions Studio")
10749
+ parser.add_argument("--understand", metavar="BUNDLE", help="start an interactive learning session for an instruction bundle; list with agent-bios understand list")
10750
+ parser.add_argument("--instructions-domains", "--corpus-domains", help="domain selection for this activated session only")
10751
+ parser.add_argument("--instructions-native", "--corpus-native", action="store_true", help="opt into selected instructions hooks on either host and Claude native agents for this session only")
10736
10752
  parser.add_argument(
10737
10753
  "--exclude-global-instructions",
10738
10754
  action="store_true",
10739
10755
  help="omit only personal global AGENTS.md/CLAUDE.md files and imports for this private Claude session",
10740
10756
  )
10741
- parser.add_argument("--resume-session", help="resume a host session with its pinned corpus")
10757
+ parser.add_argument("--resume-session", help="resume a host session with its pinned instructions")
10742
10758
  parser.add_argument(
10743
10759
  "--verify-receipts", nargs=2, metavar=("PLAN", "RECEIPTS"),
10744
10760
  help="adjudicate a ReviewPlan/v1 record against a ReviewReceipts/v1 bundle and exit",
@@ -10810,14 +10826,14 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
10810
10826
  hostless = (
10811
10827
  args.verify_receipts or args.emit_receipt or args.fold_receipts
10812
10828
  or args.check_adapter or args.compile_criterion or args.check_findings
10813
- or args.check_schema_flag or args.corpus
10829
+ or args.check_schema_flag or args.instructions
10814
10830
  )
10815
10831
  if args.host is None and not hostless:
10816
10832
  parser.error("the following arguments are required: host")
10817
- if args.understand and (args.preset or args.custom or args.corpus or args.resume_session
10818
- or args.corpus_native or args.corpus_domains is not None
10833
+ if args.understand and (args.preset or args.custom or args.instructions or args.resume_session
10834
+ or args.instructions_native or args.instructions_domains is not None
10819
10835
  or args.exclude_global_instructions or args.forward):
10820
- parser.error("--understand selects its own learning setup and cannot be combined with preset, corpus, resume, custom, global-exclusion or forwarded options")
10836
+ parser.error("--understand selects its own learning setup and cannot be combined with preset, instructions, resume, custom, global-exclusion or forwarded options")
10821
10837
  if args.evidence and not args.emit_receipt:
10822
10838
  parser.error("--evidence only applies to --emit-receipt")
10823
10839
  if args.packet and not args.verify_receipts:
@@ -11054,8 +11070,8 @@ def _tolerate_narrow_stdout() -> None:
11054
11070
  def main(argv: list[str], *, bundled_ui: bool = False) -> int:
11055
11071
  _tolerate_narrow_stdout()
11056
11072
  args = parse_args(drop_check_adapter_separator(argv))
11057
- if args.corpus:
11058
- open_corpus_studio()
11073
+ if args.instructions:
11074
+ open_instructions_studio()
11059
11075
  return 0
11060
11076
  if args.verify_receipts:
11061
11077
  return verify_receipts_command(
@@ -11080,7 +11096,7 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
11080
11096
  return check_adapter_command(args.check_adapter[0], args.check_adapter[1:])
11081
11097
  config_path = args.config.expanduser()
11082
11098
  generation = None
11083
- if private_corpus_enabled():
11099
+ if private_instructions_enabled():
11084
11100
  bare = not args.preset and not args.custom and not args.understand and not args.dry_run and (
11085
11101
  args.no_tui or bool(args.forward) or os.environ.get("AGENT_LAUNCH_TUI") == "0"
11086
11102
  or not (sys.stdin.isatty() and sys.stdout.isatty()))
@@ -11090,7 +11106,7 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
11090
11106
  config = load_config(config_path)
11091
11107
  command, bare_args = resolve_backend(config, args.host)
11092
11108
  if args.exclude_global_instructions and not args.resume_session:
11093
- if not private_corpus_enabled():
11109
+ if not private_instructions_enabled():
11094
11110
  raise LaunchError(
11095
11111
  "excluding global instruction files requires an agent-bios activated session"
11096
11112
  )
@@ -11107,30 +11123,30 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
11107
11123
  for preset in config.get("presets", {}).values():
11108
11124
  if isinstance(preset, dict):
11109
11125
  preset["include_global_instructions"] = False
11110
- if args.corpus_native and not private_corpus_enabled():
11111
- raise LaunchError("--corpus-native requires a private installation; run agent-bios install first")
11112
- if private_corpus_enabled():
11126
+ if args.instructions_native and not private_instructions_enabled():
11127
+ raise LaunchError("--instructions-native requires a private installation; run agent-bios install first")
11128
+ if private_instructions_enabled():
11113
11129
  # Private templates retain the native host's config home and registrations.
11114
- private_root = corpus_package_root()
11130
+ private_root = instructions_package_root()
11115
11131
  os.environ["AGENT_BIOS_PACKAGE_ROOT"] = str(private_root)
11116
11132
  config["hosts"]["codex"]["agent_templates"] = {
11117
11133
  tier: str(private_root / "codex/agents" / f"{tier}.toml") for tier in SPAWNABLE_TIERS
11118
11134
  }
11119
11135
  if args.resume_session:
11120
11136
  if (
11121
- args.corpus_native
11122
- or args.corpus_domains is not None
11137
+ args.instructions_native
11138
+ or args.instructions_domains is not None
11123
11139
  or args.exclude_global_instructions
11124
11140
  ):
11125
11141
  raise LaunchError(
11126
- "resume uses its pinned corpus/native/global-instruction configuration; "
11142
+ "resume uses its pinned instructions/native/global-instruction configuration; "
11127
11143
  "start a new session to change it"
11128
11144
  )
11129
- if not private_corpus_enabled():
11130
- raise LaunchError("a corpus-pinned resume requires a private installation")
11131
- store = corpus_store()
11132
- import corpus_session
11133
- return corpus_session.launch(command, [], store.state_root, args.host, {},
11145
+ if not private_instructions_enabled():
11146
+ raise LaunchError("an instruction-pinned resume requires a private installation")
11147
+ store = instructions_store()
11148
+ import instructions_session
11149
+ return instructions_session.launch(command, [], store.state_root, args.host, {},
11134
11150
  resume_id=args.resume_session)
11135
11151
  nudge = session_distill_nudge(config)
11136
11152
  if nudge:
@@ -11148,11 +11164,11 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
11148
11164
  bypass = args.no_tui or bool(args.forward) or os.environ.get("AGENT_LAUNCH_TUI") == "0"
11149
11165
  if (bypass or not tty) and not args.preset and not args.custom and not args.understand and not args.dry_run:
11150
11166
  if (
11151
- args.corpus_native
11152
- or args.corpus_domains is not None
11167
+ args.instructions_native
11168
+ or args.instructions_domains is not None
11153
11169
  or args.exclude_global_instructions
11154
11170
  ):
11155
- raise LaunchError("corpus launch options require --preset NAME or an interactive configured launch")
11171
+ raise LaunchError("instructions launch options require --preset NAME or an interactive configured launch")
11156
11172
  # The bare launch: nothing decides policy but the backend's own bare-launch
11157
11173
  # arguments. Every other path below projects the preset's policy instead.
11158
11174
  exec_backend(command, [*bare_args, *args.forward])
@@ -11212,26 +11228,26 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
11212
11228
  shell_dry_run=args.dry_run,
11213
11229
  )
11214
11230
  break
11215
- except CorpusApplyRequested as request:
11231
+ except InstructionsApplyRequested as request:
11216
11232
  # The Textual app is already torn down; the installer owns the
11217
11233
  # terminal for the duration, and the picker re-opens on a fresh
11218
11234
  # status projection afterwards.
11219
- run_corpus_apply(request.selection)
11235
+ run_instructions_apply(request.selection)
11220
11236
  continue
11221
- except CorpusStudioRequested:
11222
- open_corpus_studio()
11237
+ except InstructionsStudioRequested:
11238
+ open_instructions_studio()
11223
11239
  continue
11224
11240
  except UnderstandRequested as request:
11225
11241
  plan = build_understand_plan(config, args.host, request.bundle_id)
11226
11242
  break
11227
11243
  learning = None
11228
11244
  if plan.get("understand_bundle"):
11229
- if args.forward or args.corpus_native or args.corpus_domains is not None:
11230
- raise LaunchError("understand! cannot use forwarded arguments or native corpus activation")
11245
+ if args.forward or args.instructions_native or args.instructions_domains is not None:
11246
+ raise LaunchError("understand! cannot use forwarded arguments or native instructions activation")
11231
11247
  learning = understand_manager().show(plan["understand_bundle"])
11232
11248
  validate_review_setup(plan)
11233
11249
  if not plan.get("include_global_instructions", True):
11234
- if not private_corpus_enabled():
11250
+ if not private_instructions_enabled():
11235
11251
  raise LaunchError(
11236
11252
  "excluding global instruction files requires an agent-bios activated session"
11237
11253
  )
@@ -11246,19 +11262,19 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
11246
11262
  )
11247
11263
  snapshot = None
11248
11264
  store = None
11249
- if private_corpus_enabled() and not plan_projects_nothing(plan):
11250
- store = corpus_store()
11265
+ if private_instructions_enabled() and not plan_projects_nothing(plan):
11266
+ store = instructions_store()
11251
11267
  selected = None
11252
- if args.corpus_domains is not None:
11253
- raw = [x.strip() for x in args.corpus_domains.split(",") if x.strip()]
11268
+ if args.instructions_domains is not None:
11269
+ raw = [x.strip() for x in args.instructions_domains.split(",") if x.strip()]
11254
11270
  if "none" in raw and raw != ["none"]:
11255
- raise LaunchError("--corpus-domains none cannot be combined with other domains")
11271
+ raise LaunchError("--instructions-domains none cannot be combined with other domains")
11256
11272
  selected = [] if raw == ["none"] else [
11257
11273
  x if x.startswith("@") else f"@agent-bios/core/{x}" for x in raw
11258
11274
  ]
11259
11275
  snapshot = _snapshot_from_config(store, config_path, generation, args.host, selected,
11260
- dry_run=args.dry_run, native=args.corpus_native)
11261
- plan["corpus_instruction_text"] = snapshot["instruction_text"]
11276
+ dry_run=args.dry_run, native=args.instructions_native)
11277
+ plan["instructions_instruction_text"] = snapshot["instruction_text"]
11262
11278
  projected_args = project_args(plan, materialize_agents=not args.dry_run)
11263
11279
  collisions = forwarded_collisions(projected_args, args.forward)
11264
11280
  if collisions:
@@ -11271,8 +11287,8 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
11271
11287
  if learning is not None and args.dry_run:
11272
11288
  projected += [understand_initial_prompt("<pinned-understand-session-prompt>")]
11273
11289
  if snapshot is not None and args.dry_run:
11274
- import corpus_session
11275
- projected = corpus_session.compose_argv(
11290
+ import instructions_session
11291
+ projected = instructions_session.compose_argv(
11276
11292
  command,
11277
11293
  projected,
11278
11294
  args.host,
@@ -11281,17 +11297,17 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
11281
11297
  )
11282
11298
  summary_stream = sys.stdout if tty or args.dry_run else sys.stderr
11283
11299
  if snapshot is not None:
11284
- print(f" Corpus snapshot {snapshot['content_ref']} · private · next session only", file=summary_stream)
11285
- if args.corpus_native:
11300
+ print(f" Instructions snapshot {snapshot['content_ref']} · private · next session only", file=summary_stream)
11301
+ if args.instructions_native:
11286
11302
  plugins = snapshot.get("assets", {}).get("claude_plugins", [])
11287
11303
  hooks = snapshot.get("assets", {}).get("codex_hooks", {})
11288
11304
  if args.host == "codex":
11289
11305
  count = sum(len(group["hooks"]) for groups in hooks.values() for group in groups)
11290
- print(f" Native corpus opt-in: {count} session-only hook(s); Codex enablement and /hooks trust review apply.", file=summary_stream)
11306
+ print(f" Native instructions opt-in: {count} session-only hook(s); Codex enablement and /hooks trust review apply.", file=summary_stream)
11291
11307
  else:
11292
- print(f" Native corpus opt-in: {len(plugins)} session-only plugin(s); selected hook code can execute.", file=summary_stream)
11308
+ print(f" Native instructions opt-in: {len(plugins)} session-only plugin(s); selected hook code can execute.", file=summary_stream)
11293
11309
  for unavailable in snapshot.get("unavailable", []):
11294
- print(f" Corpus unavailable: {unavailable}", file=summary_stream)
11310
+ print(f" Instructions unavailable: {unavailable}", file=summary_stream)
11295
11311
  if learning is not None:
11296
11312
  print(f" Understand! {learning['title']} · {learning['source_ref']}", file=summary_stream)
11297
11313
  print(" Learning Pinned bundle as reference material; native global files remain unchanged.", file=summary_stream)
@@ -11327,8 +11343,8 @@ def main(argv: list[str], *, bundled_ui: bool = False) -> int:
11327
11343
  env["AGENT_BIOS_UNDERSTAND_SESSION"] = session["session_id"]
11328
11344
  summary_stream.flush()
11329
11345
  if snapshot is not None:
11330
- import corpus_session
11331
- return corpus_session.launch(command, projected, store.state_root, args.host,
11346
+ import instructions_session
11347
+ return instructions_session.launch(command, projected, store.state_root, args.host,
11332
11348
  snapshot, env=env,
11333
11349
  include_global_instructions=plan["include_global_instructions"])
11334
11350
  exec_backend(command, projected, env)