agent-bios 0.18.0 → 0.19.1

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 (55) hide show
  1. package/DEPENDENCIES.md +236 -80
  2. package/INSTALL.md +112 -0
  3. package/README.md +187 -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/claude/skills/understand/SKILL.md +52 -22
  9. package/codex/AGENTS.md +1 -1
  10. package/codex/guides/cli-multi-model-workflow.md +1 -1
  11. package/codex/guides/learning-flow.md +23 -12
  12. package/codex/guides/session-distill-workflow.md +22 -12
  13. package/compose/app_bridge/SKILL.md +75 -0
  14. package/compose/app_bridge/agents/openai.yaml +2 -0
  15. package/compose/app_bridge/scripts/bridge.py +76 -0
  16. package/compose/bootstrap/SKILL.md +12 -1
  17. package/compose/corpus.py +31 -9
  18. package/compose/corpus_app.py +456 -0
  19. package/compose/corpus_import.py +529 -0
  20. package/compose/corpus_install.py +202 -18
  21. package/compose/corpus_session.py +27 -0
  22. package/compose/corpus_setup.py +676 -0
  23. package/compose/corpus_setup_cli.py +585 -0
  24. package/compose/corpus_setup_i18n.py +324 -0
  25. package/compose/corpus_setup_ui.py +647 -0
  26. package/compose/corpus_store.py +213 -32
  27. package/compose/corpus_transaction.py +43 -10
  28. package/compose/corpus_ui_runtime.py +278 -0
  29. package/compose/corpus_understand.py +173 -22
  30. package/compose/setup/START.md +158 -0
  31. package/compose/ui_runtime/linkify_it_py-2.2.0-py3-none-any.whl +0 -0
  32. package/compose/ui_runtime/manifest.json +238 -0
  33. package/compose/ui_runtime/markdown_it_py-4.2.0-py3-none-any.whl +0 -0
  34. package/compose/ui_runtime/mdit_py_plugins-0.6.1-py3-none-any.whl +0 -0
  35. package/compose/ui_runtime/mdurl-0.1.2-py3-none-any.whl +0 -0
  36. package/compose/ui_runtime/platformdirs-4.11.8-py3-none-any.whl +0 -0
  37. package/compose/ui_runtime/pygments-2.21.0-py3-none-any.whl +0 -0
  38. package/compose/ui_runtime/rich-15.0.0-py3-none-any.whl +0 -0
  39. package/compose/ui_runtime/textual-8.2.8-py3-none-any.whl +0 -0
  40. package/compose/ui_runtime/typing_extensions-4.16.0-py3-none-any.whl +0 -0
  41. package/docs/advanced-launch.md +131 -0
  42. package/docs/assets/corpus-studio.svg +227 -0
  43. package/docs/corpus.md +117 -0
  44. package/docs/recovery.md +201 -0
  45. package/docs/session-model.md +120 -0
  46. package/docs/setup.md +206 -0
  47. package/docs/understand.md +88 -0
  48. package/install.sh +75 -46
  49. package/launch/agent-launch.py +99 -52
  50. package/launch/provision-venv.sh +44 -13
  51. package/learn/collect-learning.py +14 -5
  52. package/learn/learning.schema.json +2 -2
  53. package/package.json +14 -2
  54. package/provenance.json +1 -1
  55. package/wrappers/claude-run.sh +10 -13
@@ -21,9 +21,9 @@ import uuid
21
21
  from typing import Any, Iterator
22
22
 
23
23
  try:
24
- from corpus_transaction import transaction_lock, guard_pending, pending_operations, operation_scope_active
24
+ from corpus_transaction import transaction_lock, guard_pending, pending_operations, operation_scope_active, reject_symlink_ancestors
25
25
  except ImportError:
26
- from .corpus_transaction import transaction_lock, guard_pending, pending_operations, operation_scope_active
26
+ from .corpus_transaction import transaction_lock, guard_pending, pending_operations, operation_scope_active, reject_symlink_ancestors
27
27
 
28
28
  SCHEMA_VERSION = 1
29
29
  LOCAL_PACKAGE = "@local/personal"
@@ -311,6 +311,7 @@ class CorpusStore:
311
311
  # Optional, so reading an older state does not change its revision or the
312
312
  # exact before/after documents used by prepared-transaction recovery.
313
313
  self._enabled_overrides(state)
314
+ self._selection_mode(state, {})
314
315
  return state
315
316
 
316
317
  @staticmethod
@@ -322,6 +323,13 @@ class CorpusStore:
322
323
  raise CorpusStoreError("personal state has invalid enabled_overrides")
323
324
  return values
324
325
 
326
+ @staticmethod
327
+ def _selection_mode(user: dict[str, Any], defaults: dict[str, Any], requested: str | None = None) -> str:
328
+ value = requested if requested is not None else user.get("selection_mode", defaults.get("selection_mode", "default"))
329
+ if not isinstance(value, str) or value not in {"default", "selected", "none"}:
330
+ raise ValidationError("selection_mode must be default, selected, or none")
331
+ return value
332
+
325
333
  def _write_transaction(self, tx_id: str, record: dict[str, Any]) -> None:
326
334
  _atomic_write(self.runtime / "transactions" / tx_id / "journal.json", record)
327
335
 
@@ -474,6 +482,11 @@ class CorpusStore:
474
482
  for raw in source:
475
483
  item = self._validate_item(raw, allow_origin=True)
476
484
  items[item["ref"]] = item
485
+ items = self._overlay_user_items(items, user, host, include_suppressed)
486
+ return [self._normalize_content(item, allow_legacy=True) for item in items.values()], inventory, defaults, baseline_ref
487
+
488
+ def _overlay_user_items(self, items: dict[str, dict[str, Any]], user: dict[str, Any],
489
+ host: str | None = None, include_suppressed: bool = False) -> dict[str, dict[str, Any]]:
477
490
  for ref, raw in user["items"].items():
478
491
  item = self._validate_item(raw, allow_origin=True)
479
492
  if item["ref"] != ref or item["package_id"] != LOCAL_PACKAGE:
@@ -508,7 +521,7 @@ class CorpusStore:
508
521
  items[ref]["active"] = False
509
522
  else:
510
523
  items.pop(ref, None)
511
- return [self._normalize_content(item, allow_legacy=True) for item in items.values()], inventory, defaults, baseline_ref
524
+ return items
512
525
 
513
526
  def _resolve_promotions(
514
527
  self, selected: list[dict[str, Any]], user: dict[str, Any], host: str, baseline_ref: str
@@ -645,7 +658,7 @@ class CorpusStore:
645
658
  raise ValidationError("selection must be a list of qualified refs/domains")
646
659
  packages = {p.get("package_id") for p in inventory.get("packages", []) if isinstance(p, dict)}
647
660
  for value in selection:
648
- if value == "all":
661
+ if value == "all" or value in packages:
649
662
  continue
650
663
  if ":" in value:
651
664
  continue
@@ -704,6 +717,18 @@ class CorpusStore:
704
717
  if package not in packages or domain not in packages[package]:
705
718
  raise ValidationError(f"unknown corpus selection domain: {value}")
706
719
 
720
+ def _selection_subjects(self, runtime: dict[str, Any], user: dict[str, Any],
721
+ items: list[dict[str, Any]], selection: list[str] | None) -> list[dict[str, Any]]:
722
+ """Validate stored host-qualified selections without delivering another host's items."""
723
+ subjects = list(items)
724
+ refs = {item["ref"] for item in subjects}
725
+ for host in ("claude", "codex"):
726
+ prefix = f"@local/learnings-{host}:"
727
+ if any(value.startswith(prefix) and value not in refs for value in selection or []):
728
+ subjects.extend(item for item in self._effective_items(runtime, user, host=host)[0]
729
+ if item.get("active", True))
730
+ return subjects
731
+
707
732
  def _validate_candidate_projection(self, runtime: dict[str, Any], user: dict[str, Any]) -> None:
708
733
  """Use the real compiler as a plan validator without publishing output."""
709
734
  catalog = self._catalog_module()
@@ -713,8 +738,10 @@ class CorpusStore:
713
738
  items, _inventory, defaults, _baseline = self._effective_items(runtime, user, host=host)
714
739
  active = [item for item in items if item.get("active", True) is not False]
715
740
  selection = self._effective_selection(user, defaults, None)
716
- self._validate_snapshot_selection(selection, _inventory, active)
717
- selected = self._selected_items(active, selection, self._enabled_overrides(user))
741
+ self._validate_snapshot_selection(selection, _inventory,
742
+ self._selection_subjects(runtime, user, active, selection))
743
+ selected = self._selected_items(active, selection, self._enabled_overrides(user),
744
+ mode=self._selection_mode(user, defaults), host=host)
718
745
  self._require_resolved(selected)
719
746
  catalog.compile_items(_copy_json(selected), Path(temp) / host, host)
720
747
  except Exception as exc:
@@ -802,12 +829,53 @@ class CorpusStore:
802
829
 
803
830
  # ---- public read API ----------------------------------------------------
804
831
 
805
- def install(self, domains: list[str] | None = None) -> dict[str, Any]:
832
+ def local_item_counts(self) -> dict[str, int]:
833
+ """Count retained local content without changing state or session selection."""
834
+ reject_symlink_ancestors(self.state_root)
835
+ reject_symlink_ancestors(self.user_root)
836
+ for directory in (self.state_root, self.runtime, self.user_root):
837
+ if directory.exists() and not directory.is_dir():
838
+ raise CorpusStoreError(f"retained local corpus root is not a directory: {directory}")
839
+ sources = [self._user_state_path, *(self.user_root / "learnings" / host / "events.jsonl"
840
+ for host in ("claude", "codex"))]
841
+ for path in sources:
842
+ reject_symlink_ancestors(path)
843
+ for parent in path.parents:
844
+ if parent == self.user_root.parent:
845
+ break
846
+ if parent.exists() and not parent.is_dir():
847
+ raise CorpusStoreError(f"retained local corpus directory is not a directory: {parent}")
848
+ if path.exists() and not path.is_file():
849
+ raise CorpusStoreError(f"retained local corpus source is not a file: {path}")
850
+ if pending_operations(self.state_root):
851
+ raise CorpusStoreError("retained local corpus has a pending transaction; finish recovery before inspection")
852
+ try:
853
+ user = self._user_state()
854
+ for host in ("claude", "codex"):
855
+ suppressed = user["learning_suppressions"].get(host, [])
856
+ if not isinstance(suppressed, list) or not all(isinstance(value, str) for value in suppressed):
857
+ raise CorpusStoreError(f"personal state has invalid learning suppressions for {host}")
858
+ items: dict[str, dict[str, Any]] = {}
859
+ for host in ("claude", "codex"):
860
+ items.update(self._overlay_user_items({}, user, host))
861
+ counts: dict[str, int] = {}
862
+ for item in items.values():
863
+ if item.get("active", True) is not False:
864
+ package = item["package_id"]
865
+ counts[package] = counts.get(package, 0) + 1
866
+ return counts
867
+ except (AttributeError, KeyError, TypeError, ValueError) as exc:
868
+ raise CorpusStoreError("invalid retained local corpus state") from exc
869
+
870
+ def install(self, domains: list[str] | None = None, *, selection_mode: str | None = None,
871
+ replace_selection: bool = False) -> dict[str, Any]:
806
872
  """Install one immutable validated baseline tuple without touching user data."""
807
873
  with self._lock():
808
- return self.commit_install(self.prepare_install(domains))
874
+ return self.commit_install(self.prepare_install(domains, selection_mode=selection_mode,
875
+ replace_selection=replace_selection))
809
876
 
810
- def prepare_install(self, domains: list[str] | None = None) -> dict[str, Any]:
877
+ def prepare_install(self, domains: list[str] | None = None, *, selection_mode: str | None = None,
878
+ replace_selection: bool = False) -> dict[str, Any]:
811
879
  """Stage a validated baseline and source plan without advancing any pointer."""
812
880
  with self._lock():
813
881
  self._recover_locked()
@@ -820,9 +888,15 @@ class CorpusStore:
820
888
  items = [self._validate_item(item, allow_origin=True) for item in raw_items]
821
889
  if len({item["ref"] for item in items}) != len(items):
822
890
  raise ValidationError("catalog has duplicate corpus refs")
823
- defaults = {"schema_version": SCHEMA_VERSION,
824
- "selection": self._normalized_install_selection(domains, catalog) +
825
- [LOCAL_PACKAGE, "@local/learnings-claude", "@local/learnings-codex"]}
891
+ mode = self._selection_mode({}, {}, selection_mode)
892
+ selected = self._normalized_install_selection(domains, catalog)
893
+ if mode == "none" and selected:
894
+ raise ValidationError("no-corpus installation cannot include selection targets")
895
+ defaults = {"schema_version": SCHEMA_VERSION, "selection": selected}
896
+ if mode == "default":
897
+ defaults["selection"] += [LOCAL_PACKAGE, "@local/learnings-claude", "@local/learnings-codex"]
898
+ if selection_mode is not None:
899
+ defaults["selection_mode"] = mode
826
900
  promotion_path = self.repo / "learn" / "promotions.json"
827
901
  promotions = _json_read(promotion_path, {"version": 0, "promotions": []})
828
902
  if not isinstance(promotions, dict) or not isinstance(promotions.get("promotions"), list):
@@ -849,6 +923,11 @@ class CorpusStore:
849
923
  # successful-install record changes.
850
924
  next_user = self._rebase_overlays(old_inventory, catalog, user)
851
925
  before = {"runtime": _copy_json(runtime), "user": _copy_json(user)}
926
+ if replace_selection:
927
+ next_user = _copy_json(next_user)
928
+ next_user["selection"] = _copy_json(defaults["selection"])
929
+ next_user["selection_mode"] = mode
930
+ next_user.pop("enabled_overrides", None)
852
931
  _atomic_write(root / "inventory.json", catalog)
853
932
  _atomic_write(root / "defaults.json", defaults)
854
933
  _atomic_write(root / "promotions.json", promotions)
@@ -909,6 +988,7 @@ class CorpusStore:
909
988
  "revision": revision, "baseline_count": len(refs),
910
989
  "personal_items": len(user["items"]), "overrides": len(user["overrides"]),
911
990
  "tombstones": len(user["tombstones"]), "selection": self._effective_selection(user, defaults, None),
991
+ "selection_mode": self._selection_mode(user, defaults),
912
992
  "enabled_overrides": _copy_json(self._enabled_overrides(user)),
913
993
  }
914
994
 
@@ -925,7 +1005,8 @@ class CorpusStore:
925
1005
  overrides = self._enabled_overrides(user)
926
1006
  selection = self._effective_selection(user, defaults, None)
927
1007
  enabled = {item["ref"] for item in self._selected_items(
928
- [item for item in effective.values() if item.get("active", True) is not False], selection, overrides)}
1008
+ [item for item in effective.values() if item.get("active", True) is not False], selection, overrides,
1009
+ mode=self._selection_mode(user, defaults))}
929
1010
  revision = self._authoring_revision(runtime, user)
930
1011
  rows: list[dict[str, Any]] = []
931
1012
  for ref in sorted(set(source) | set(user["items"]) | set(effective)):
@@ -993,11 +1074,12 @@ class CorpusStore:
993
1074
  raise CorpusStoreError("could not allocate an unused personal identity")
994
1075
 
995
1076
  def _prepare_operation(self, payload: dict[str, Any], runtime: dict[str, Any], user: dict[str, Any],
996
- *, allocated_item_id: str | None = None) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
1077
+ *, allocated_item_id: str | None = None,
1078
+ allocated_item_ids: list[str] | None = None) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
997
1079
  if not isinstance(payload, dict):
998
1080
  raise ValidationError("plan payload must be an object")
999
1081
  op = payload.get("operation", payload.get("op"))
1000
- if op not in {"create", "update", "remove", "restore", "recover", "reset", "rollback", "select", "enable"}:
1082
+ if not isinstance(op, str) or op not in {"create", "update", "remove", "restore", "recover", "reset", "rollback", "select", "enable", "import"}:
1001
1083
  raise ValidationError("unknown corpus operation")
1002
1084
  allowed = {
1003
1085
  "create": {"operation", "op", "item", "package_id", "expected_revision"},
@@ -1007,8 +1089,9 @@ class CorpusStore:
1007
1089
  "recover": {"operation", "op", "ref", "expected_revision"},
1008
1090
  "reset": {"operation", "op", "expected_revision"},
1009
1091
  "rollback": {"operation", "op", "baseline_ref", "history_id", "expected_revision"},
1010
- "select": {"operation", "op", "selection", "expected_revision"},
1092
+ "select": {"operation", "op", "selection", "selection_mode", "expected_revision"},
1011
1093
  "enable": {"operation", "op", "items", "expected_revision"},
1094
+ "import": {"operation", "op", "capture_id", "candidates", "excluded", "expected_revision"},
1012
1095
  }[op]
1013
1096
  unknown = set(payload) - allowed
1014
1097
  if unknown:
@@ -1021,7 +1104,31 @@ class CorpusStore:
1021
1104
  items, inventory, defaults, _baseline_ref = self._effective_items(runtime, user)
1022
1105
  effective = {item["ref"]: item for item in items}
1023
1106
  details: dict[str, Any] = {"operation": op}
1024
- if op == "create":
1107
+ if op == "import":
1108
+ import importlib
1109
+ name = "compose.corpus_import" if __package__ else "corpus_import"
1110
+ prepared_import = importlib.import_module(name).prepare_items(self, payload, user)
1111
+ refs = prepared_import["existing_refs"]
1112
+ receipt = prepared_import["receipt"]
1113
+ rows = prepared_import["items"]
1114
+ if rows:
1115
+ if allocated_item_ids is None or len(allocated_item_ids) != len(rows):
1116
+ raise ValidationError("import needs recorded runtime-owned identities")
1117
+ refs = []
1118
+ for raw, item_id in zip(rows, allocated_item_ids):
1119
+ _safe_part(item_id, "personal item id")
1120
+ raw = _copy_json(raw)
1121
+ raw.update(package_id=LOCAL_PACKAGE, item_id=item_id, ref=f"{LOCAL_PACKAGE}:{item_id}")
1122
+ item = self._validate_item(self._normalize_content(raw), allow_origin=True)
1123
+ if item["ref"] in effective or item["ref"] in next_user["items"]:
1124
+ raise ValidationError("import identity is already in use")
1125
+ next_user["items"][item["ref"]] = item
1126
+ refs.append(item["ref"])
1127
+ receipt["refs"] = refs
1128
+ receipt["item_digests"] = {ref: _digest(next_user["items"][ref]) for ref in refs}
1129
+ next_user.setdefault("imports", {})[receipt["request_digest"]] = receipt
1130
+ details.update(refs=refs, import_receipt=receipt, already_imported=not bool(rows))
1131
+ elif op == "create":
1025
1132
  raw = _copy_json(payload.get("item"))
1026
1133
  if not isinstance(raw, dict):
1027
1134
  raise ValidationError("create needs item")
@@ -1168,6 +1275,14 @@ class CorpusStore:
1168
1275
  details["items"] = _copy_json(choices)
1169
1276
  elif op == "select":
1170
1277
  selection = self._validate_selection(payload.get("selection"), inventory)
1278
+ if "selection_mode" in payload:
1279
+ if payload["selection_mode"] is None:
1280
+ raise ValidationError("selection_mode cannot be null")
1281
+ mode = self._selection_mode({}, {}, payload["selection_mode"])
1282
+ if mode == "none" and selection:
1283
+ raise ValidationError("no-corpus selection cannot include targets")
1284
+ next_user["selection_mode"] = mode
1285
+ details["selection_mode"] = mode
1171
1286
  next_user["selection"] = selection
1172
1287
  details["selection"] = selection
1173
1288
  elif op == "reset":
@@ -1181,6 +1296,8 @@ class CorpusStore:
1181
1296
  next_runtime["selected_baseline_ref"] = latest
1182
1297
  _inv, latest_defaults = self._read_baseline(latest)
1183
1298
  next_user["selection"] = latest_defaults.get("selection")
1299
+ if "selection_mode" in latest_defaults:
1300
+ next_user["selection_mode"] = latest_defaults["selection_mode"]
1184
1301
  details["baseline_ref"] = latest
1185
1302
  elif op == "rollback":
1186
1303
  history_id = payload.get("history_id")
@@ -1209,8 +1326,22 @@ class CorpusStore:
1209
1326
  self._recover_locked()
1210
1327
  runtime, user = self._runtime_state(), self._user_state()
1211
1328
  before = self._authoring_revision(runtime, user)
1212
- allocated = self._next_personal_id(user) if isinstance(payload, dict) and payload.get("operation", payload.get("op")) == "create" else None
1213
- next_runtime, next_user, details = self._prepare_operation(payload, runtime, user, allocated_item_id=allocated)
1329
+ op = payload.get("operation", payload.get("op")) if isinstance(payload, dict) else None
1330
+ allocated = self._next_personal_id(user) if op == "create" else None
1331
+ allocated_many = None
1332
+ if op == "import":
1333
+ import importlib
1334
+ name = "compose.corpus_import" if __package__ else "corpus_import"
1335
+ importer = importlib.import_module(name)
1336
+ payload = importer.sanitize_import_payload(payload)
1337
+ prepared_import = importer.prepare_items(self, payload, user)
1338
+ allocated_many, reserved = [], _copy_json(user)
1339
+ for _item in prepared_import["items"]:
1340
+ item_id = self._next_personal_id(reserved)
1341
+ allocated_many.append(item_id)
1342
+ reserved["items"][f"{LOCAL_PACKAGE}:{item_id}"] = {}
1343
+ next_runtime, next_user, details = self._prepare_operation(payload, runtime, user, allocated_item_id=allocated,
1344
+ allocated_item_ids=allocated_many)
1214
1345
  self._validate_candidate_projection(next_runtime, next_user)
1215
1346
  after = self._authoring_revision(next_runtime, next_user)
1216
1347
  plan_id = uuid.uuid4().hex
@@ -1222,6 +1353,8 @@ class CorpusStore:
1222
1353
  }
1223
1354
  if allocated is not None:
1224
1355
  plan["allocated_item_id"] = allocated
1356
+ if allocated_many is not None:
1357
+ plan["allocated_item_ids"] = allocated_many
1225
1358
  self._write_transaction(plan_id, {"state": "PLANNED", "plan": plan})
1226
1359
  return {key: plan[key] for key in ("schema_version", "plan_id", "expected_revision", "result_revision", "details")}
1227
1360
 
@@ -1251,11 +1384,16 @@ class CorpusStore:
1251
1384
  # Persisted pre-allocation plans already resolved their identity in after-state.
1252
1385
  ref = plan.get("details", {}).get("ref")
1253
1386
  allocated = ref.split(":", 1)[1] if isinstance(ref, str) and ref.startswith(LOCAL_PACKAGE + ":") else None
1254
- next_runtime, next_user, details = self._prepare_operation(plan["payload"], runtime, user, allocated_item_id=allocated)
1387
+ next_runtime, next_user, details = self._prepare_operation(plan["payload"], runtime, user, allocated_item_id=allocated,
1388
+ allocated_item_ids=plan.get("allocated_item_ids"))
1255
1389
  self._validate_candidate_projection(next_runtime, next_user)
1256
1390
  result = self._authoring_revision(next_runtime, next_user)
1257
1391
  if result != plan.get("result_revision"):
1258
1392
  raise CorpusStoreError("plan result changed during apply")
1393
+ if details.get("operation") == "import":
1394
+ import importlib
1395
+ name = "compose.corpus_import" if __package__ else "corpus_import"
1396
+ importlib.import_module(name).verify_import_sources(self, details)
1259
1397
  prepared = {"state": "PREPARED", "plan": plan, "prior_revision": current, "prepared_at": _utcnow()}
1260
1398
  history_id = f"{prepared['prepared_at'].replace(':', '').replace('+00:00', 'Z')}-{plan_id[:12]}"
1261
1399
  prepared["history_id"] = history_id
@@ -1279,18 +1417,42 @@ class CorpusStore:
1279
1417
  # ---- immutable snapshots and history -----------------------------------
1280
1418
 
1281
1419
  def _selected_items(self, items: list[dict[str, Any]], selection: list[str] | None,
1282
- overrides: dict[str, bool] | None = None) -> list[dict[str, Any]]:
1420
+ overrides: dict[str, bool] | None = None, *, mode: str = "default",
1421
+ host: str | None = None, cwd: str | Path | None = None) -> list[dict[str, Any]]:
1422
+ self._selection_mode({}, {}, mode)
1423
+ if mode == "none":
1424
+ return []
1283
1425
  overrides = overrides or {}
1426
+ working = Path(cwd or Path.cwd()).resolve()
1284
1427
  selected: list[dict[str, Any]] = []
1285
1428
  for item in items:
1286
- if item["ref"] in overrides:
1429
+ origin = item.get("origin", {})
1430
+ if origin.get("type") == "instruction_import":
1431
+ scope = origin.get("scope", {})
1432
+ if not isinstance(scope, dict) or scope.get("kind") not in {"global", "project"}:
1433
+ raise ValidationError("imported item has invalid source scope")
1434
+ hosts = origin.get("hosts", ["claude", "codex"])
1435
+ if not isinstance(hosts, list) or not hosts or any(value not in {"claude", "codex"} for value in hosts):
1436
+ raise ValidationError("imported item has invalid host scope")
1437
+ if host is not None and host not in hosts:
1438
+ continue
1439
+ if scope["kind"] == "project":
1440
+ root = scope.get("root")
1441
+ if not isinstance(root, str) or not Path(root).is_absolute():
1442
+ raise ValidationError("imported project root must be absolute")
1443
+ project_root = Path(root)
1444
+ if project_root.resolve() != project_root:
1445
+ raise ValidationError("imported project root is no longer canonical; review its scope")
1446
+ if not working.is_relative_to(project_root):
1447
+ continue
1448
+ if item["ref"] in overrides and (mode == "default" or overrides[item["ref"]] is False):
1287
1449
  if overrides[item["ref"]]:
1288
1450
  selected.append(item)
1289
1451
  continue
1290
1452
  if selection and "all" in selection:
1291
1453
  selected.append(item)
1292
1454
  continue
1293
- if item.get("tier") in {"core", "infra"}:
1455
+ if mode == "default" and item.get("tier") in {"core", "infra"}:
1294
1456
  selected.append(item)
1295
1457
  continue
1296
1458
  if not selection:
@@ -1303,7 +1465,8 @@ class CorpusStore:
1303
1465
  return selected
1304
1466
 
1305
1467
  def snapshot(self, host: str, selection: list[str] | None = None, dry_run: bool = False,
1306
- native: bool = False) -> dict[str, Any]:
1468
+ native: bool = False, *, selection_mode: str | None = None,
1469
+ cwd: str | Path | None = None) -> dict[str, Any]:
1307
1470
  """Compose an immutable activated-session snapshot.
1308
1471
 
1309
1472
  A dry run has no durable write path: it compiles in an OS temporary
@@ -1327,8 +1490,16 @@ class CorpusStore:
1327
1490
  items, inventory, defaults, baseline_ref = self._effective_items(runtime, user, host=host)
1328
1491
  active = [item for item in items if item.get("active", True) is not False]
1329
1492
  effective_selection = self._effective_selection(user, defaults, selection)
1330
- self._validate_snapshot_selection(effective_selection, inventory, active)
1331
- selected = self._selected_items(active, effective_selection, self._enabled_overrides(user))
1493
+ mode = self._selection_mode(user, defaults, selection_mode)
1494
+ if selection is not None and selection_mode is None and mode == "none":
1495
+ mode = "default"
1496
+ if mode == "none":
1497
+ effective_selection = []
1498
+ working = Path(cwd or Path.cwd()).resolve()
1499
+ self._validate_snapshot_selection(effective_selection, inventory,
1500
+ self._selection_subjects(runtime, user, active, effective_selection))
1501
+ selected = self._selected_items(active, effective_selection, self._enabled_overrides(user),
1502
+ mode=mode, host=host, cwd=working)
1332
1503
  self._require_resolved(selected)
1333
1504
  selected, promotion_warnings = self._resolve_promotions(selected, user, host, baseline_ref)
1334
1505
  bootstrap_path = self.repo / "compose" / "bootstrap" / "SKILL.md"
@@ -1338,6 +1509,7 @@ class CorpusStore:
1338
1509
  inputs = {
1339
1510
  "schema_version": SCHEMA_VERSION, "host": host, "baseline_ref": baseline_ref,
1340
1511
  "selection": effective_selection,
1512
+ "selection_mode": mode, "cwd": str(working),
1341
1513
  "selection_digest": _digest(effective_selection),
1342
1514
  "authoring_revision": self._authoring_revision(runtime, user),
1343
1515
  "item_digests": {item["ref"]: _digest(item) for item in sorted(selected, key=lambda x: x["ref"])},
@@ -1379,14 +1551,21 @@ class CorpusStore:
1379
1551
  files = _snapshot_relative_paths(compiled.get("files"))
1380
1552
  output = {
1381
1553
  "instruction_text": compiled.get("instruction_text", ""),
1382
- "files": sorted(set(files) | {"bootstrap/SKILL.md"}), "item_refs": compiled.get("item_refs", []),
1554
+ "files": sorted(set(files) | ({"bootstrap/SKILL.md"} if mode != "none" else set())), "item_refs": compiled.get("item_refs", []),
1383
1555
  "unavailable": compiled.get("unavailable", []) + promotion_warnings,
1384
1556
  }
1385
1557
  assets = _snapshot_assets(compiled.get("assets", {}), files)
1386
1558
  if assets:
1387
1559
  output["assets"] = assets
1388
- invocation = f"Corpus management: invoke $corpus using {root / 'bootstrap' / 'SKILL.md'}."
1389
- output["instruction_text"] = output["instruction_text"].rstrip() + "\n\n" + invocation + "\n"
1560
+ invocation = (f"Corpus management: invoke $agent-bios using {root / 'bootstrap' / 'SKILL.md'}."
1561
+ if mode != "none" else "")
1562
+ if mode == "none":
1563
+ output["instruction_text"] = ""
1564
+ for relative in files:
1565
+ if relative == "launch-content/instructions.md":
1566
+ (staging / relative).write_text("", encoding="utf-8")
1567
+ else:
1568
+ output["instruction_text"] = output["instruction_text"].rstrip() + "\n\n" + invocation + "\n"
1390
1569
  for plugin in assets.get("claude_plugins", []):
1391
1570
  for agent_path in (staging / plugin / "agents").glob("*.md"):
1392
1571
  with agent_path.open("a", encoding="utf-8") as agent_file:
@@ -1394,9 +1573,10 @@ class CorpusStore:
1394
1573
  if dry_run:
1395
1574
  output["instruction_text"] = output["instruction_text"].replace(str(staging), str(root))
1396
1575
  else:
1397
- bootstrap_target = staging / "bootstrap" / "SKILL.md"
1398
- bootstrap_target.parent.mkdir(parents=True, exist_ok=True)
1399
- bootstrap_target.write_bytes(bootstrap_path.read_bytes())
1576
+ if mode != "none":
1577
+ bootstrap_target = staging / "bootstrap" / "SKILL.md"
1578
+ bootstrap_target.parent.mkdir(parents=True, exist_ok=True)
1579
+ bootstrap_target.write_bytes(bootstrap_path.read_bytes())
1400
1580
  _rewrite_staged_paths(staging, root)
1401
1581
  output["instruction_text"] = output["instruction_text"].replace(str(staging), str(root))
1402
1582
  _atomic_write(staging / "inventory.json", {"inputs": inputs, "items": selected})
@@ -1419,6 +1599,7 @@ class CorpusStore:
1419
1599
  shutil.rmtree(staging)
1420
1600
  return {"content_ref": content_ref, "path": str(root), "instruction_text": output["instruction_text"],
1421
1601
  "revision": inputs["authoring_revision"], "unavailable": output.get("unavailable", []),
1602
+ "item_refs": output.get("item_refs", []), "selection_mode": mode,
1422
1603
  "assets": output.get("assets", {})}
1423
1604
 
1424
1605
  def history(self, ref: str | None = None) -> list[dict[str, Any]]:
@@ -14,6 +14,7 @@ import hashlib
14
14
  import json
15
15
  import os
16
16
  from pathlib import Path
17
+ import stat
17
18
  import sys
18
19
  import threading
19
20
  from typing import Any, Iterator
@@ -63,34 +64,66 @@ def _depths(name: str) -> dict[str, int]:
63
64
 
64
65
 
65
66
  @contextlib.contextmanager
66
- def transaction_lock(state_root: Path) -> Iterator[None]:
67
- """The single re-entrant cross-process lock for store and installer work."""
67
+ def _transaction_lock(state_root: Path, *, readonly: bool) -> Iterator[bool]:
68
68
  root = Path(state_root).expanduser()
69
69
  if root.is_symlink():
70
70
  raise TransactionError(f"unsafe corpus state root: {root}")
71
+ if readonly:
72
+ reject_symlink_ancestors(root)
71
73
  key = _key(root)
72
74
  depths = _depths("lock_depths")
73
75
  if depths.get(key, 0):
74
76
  depths[key] += 1
75
77
  try:
76
- yield
78
+ yield True
77
79
  finally:
78
80
  depths[key] -= 1
79
81
  return
80
- root.mkdir(parents=True, exist_ok=True, mode=0o700)
82
+ if not readonly:
83
+ root.mkdir(parents=True, exist_ok=True, mode=0o700)
81
84
  lock = root / ".corpus-store.lock"
82
85
  if lock.is_symlink():
83
86
  raise TransactionError(f"unsafe corpus transaction lock: {lock}")
84
- flags = os.O_CREAT | os.O_RDWR | getattr(os, "O_NOFOLLOW", 0)
85
- descriptor = os.open(lock, flags, 0o600)
86
- with os.fdopen(descriptor, "a+", encoding="utf-8") as handle:
87
- fcntl.flock(handle, fcntl.LOCK_EX)
87
+ flags = (os.O_RDONLY | os.O_NONBLOCK if readonly else os.O_CREAT | os.O_RDWR) | getattr(os, "O_NOFOLLOW", 0)
88
+ try:
89
+ descriptor = os.open(lock, flags, 0o600)
90
+ except FileNotFoundError:
91
+ if not readonly:
92
+ raise
93
+ yield False
94
+ return
95
+ try:
96
+ if readonly and not stat.S_ISREG(os.fstat(descriptor).st_mode):
97
+ raise TransactionError(f"unsafe corpus transaction lock: {lock}")
98
+ try:
99
+ fcntl.flock(descriptor, fcntl.LOCK_EX | (fcntl.LOCK_NB if readonly else 0))
100
+ except BlockingIOError:
101
+ if not readonly:
102
+ raise
103
+ yield False
104
+ return
88
105
  depths[key] = 1
89
106
  try:
90
- yield
107
+ yield True
91
108
  finally:
92
109
  depths.pop(key, None)
93
- fcntl.flock(handle, fcntl.LOCK_UN)
110
+ fcntl.flock(descriptor, fcntl.LOCK_UN)
111
+ finally:
112
+ os.close(descriptor)
113
+
114
+
115
+ @contextlib.contextmanager
116
+ def transaction_lock(state_root: Path) -> Iterator[None]:
117
+ """The single re-entrant cross-process lock for store and installer work."""
118
+ with _transaction_lock(state_root, readonly=False):
119
+ yield
120
+
121
+
122
+ @contextlib.contextmanager
123
+ def try_transaction_lock(state_root: Path) -> Iterator[bool]:
124
+ """Observe under the existing lock, or defer without blocking or creating state."""
125
+ with _transaction_lock(state_root, readonly=True) as acquired:
126
+ yield acquired
94
127
 
95
128
 
96
129
  @contextlib.contextmanager