agent-bios 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/DEPENDENCIES.md +236 -80
  2. package/INSTALL.md +112 -0
  3. package/README.md +184 -524
  4. package/claude/CLAUDE.md +1 -1
  5. package/claude/guides/cli-multi-model-workflow.md +1 -1
  6. package/claude/guides/learning-flow.md +23 -12
  7. package/claude/guides/session-distill-workflow.md +22 -12
  8. package/codex/AGENTS.md +1 -1
  9. package/codex/guides/cli-multi-model-workflow.md +1 -1
  10. package/codex/guides/learning-flow.md +23 -12
  11. package/codex/guides/session-distill-workflow.md +22 -12
  12. package/compose/app_bridge/SKILL.md +75 -0
  13. package/compose/app_bridge/agents/openai.yaml +2 -0
  14. package/compose/app_bridge/scripts/bridge.py +76 -0
  15. package/compose/bootstrap/SKILL.md +12 -1
  16. package/compose/corpus.py +31 -9
  17. package/compose/corpus_app.py +456 -0
  18. package/compose/corpus_import.py +529 -0
  19. package/compose/corpus_install.py +196 -18
  20. package/compose/corpus_session.py +27 -0
  21. package/compose/corpus_setup.py +674 -0
  22. package/compose/corpus_setup_cli.py +582 -0
  23. package/compose/corpus_setup_i18n.py +318 -0
  24. package/compose/corpus_setup_ui.py +633 -0
  25. package/compose/corpus_store.py +167 -29
  26. package/compose/corpus_transaction.py +43 -10
  27. package/compose/corpus_ui_runtime.py +278 -0
  28. package/compose/setup/START.md +147 -0
  29. package/compose/ui_runtime/linkify_it_py-2.2.0-py3-none-any.whl +0 -0
  30. package/compose/ui_runtime/manifest.json +238 -0
  31. package/compose/ui_runtime/markdown_it_py-4.2.0-py3-none-any.whl +0 -0
  32. package/compose/ui_runtime/mdit_py_plugins-0.6.1-py3-none-any.whl +0 -0
  33. package/compose/ui_runtime/mdurl-0.1.2-py3-none-any.whl +0 -0
  34. package/compose/ui_runtime/platformdirs-4.11.8-py3-none-any.whl +0 -0
  35. package/compose/ui_runtime/pygments-2.21.0-py3-none-any.whl +0 -0
  36. package/compose/ui_runtime/rich-15.0.0-py3-none-any.whl +0 -0
  37. package/compose/ui_runtime/textual-8.2.8-py3-none-any.whl +0 -0
  38. package/compose/ui_runtime/typing_extensions-4.16.0-py3-none-any.whl +0 -0
  39. package/docs/advanced-launch.md +131 -0
  40. package/docs/assets/corpus-studio.svg +227 -0
  41. package/docs/corpus.md +117 -0
  42. package/docs/recovery.md +201 -0
  43. package/docs/session-model.md +120 -0
  44. package/docs/setup.md +190 -0
  45. package/docs/understand.md +40 -0
  46. package/install.sh +75 -46
  47. package/launch/agent-launch.py +91 -47
  48. package/launch/provision-venv.sh +44 -13
  49. package/learn/collect-learning.py +14 -5
  50. package/learn/learning.schema.json +2 -2
  51. package/package.json +14 -2
  52. package/provenance.json +1 -1
  53. package/wrappers/claude-run.sh +10 -13
@@ -26,6 +26,9 @@ import tomllib
26
26
  import uuid
27
27
  from typing import Any
28
28
 
29
+ if __name__ == "__main__":
30
+ sys.dont_write_bytecode = True
31
+
29
32
  from corpus_transaction import (
30
33
  _valid_release,
31
34
  TransactionError,
@@ -345,7 +348,8 @@ class CorpusInstaller:
345
348
  # CorpusStore delegates compile/load to a module name for normal package
346
349
  # execution. Bind this instance to the release's exact catalog so a
347
350
  # long-lived manager cannot retain an older release through sys.modules.
348
- store._catalog_module = lambda: self._private_module(package_root, "corpus_catalog")
351
+ catalog = self._private_module(package_root, "corpus_catalog")
352
+ store._catalog_module = lambda: catalog
349
353
  return store
350
354
 
351
355
  def _catalog(self, package_root: Path) -> dict[str, Any]:
@@ -374,7 +378,9 @@ class CorpusInstaller:
374
378
  """Keep the legacy launcher panel readable without claiming activation."""
375
379
  manifest = _read_json(package_root / "compose" / "domains.json")
376
380
  available = sorted((manifest.get("domains") or {}).keys())
377
- applied = available if selection == ["all"] else sorted(value.rsplit("/", 1)[-1] for value in selection)
381
+ package_id = manifest.get("package_id", "@agent-bios/core")
382
+ applied = available if "all" in selection or package_id in selection else [
383
+ name for name in available if f"{package_id}/{name}" in selection]
378
384
  _atomic_json(self.state_root / "corpus-status.json", {
379
385
  "repo": str(package_root), "current_version": None, "latest_version": None,
380
386
  "rolled_back_to": None, "versions": None, "summary": None,
@@ -387,7 +393,9 @@ class CorpusInstaller:
387
393
  """The status projection is part of the same publication as its record."""
388
394
  manifest = _read_json(package_root / "compose" / "domains.json")
389
395
  available = sorted((manifest.get("domains") or {}).keys())
390
- applied = available if selection == ["all"] else sorted(value.rsplit("/", 1)[-1] for value in selection)
396
+ package_id = manifest.get("package_id", "@agent-bios/core")
397
+ applied = available if "all" in selection or package_id in selection else [
398
+ name for name in available if f"{package_id}/{name}" in selection]
391
399
  return _canonical({
392
400
  "repo": str(package_root), "current_version": None, "latest_version": None,
393
401
  "rolled_back_to": None, "versions": None, "summary": None,
@@ -544,6 +552,85 @@ class CorpusInstaller:
544
552
  spec.loader.exec_module(module)
545
553
  return module.ShellIntegration(self.env, self.repo)
546
554
 
555
+ def setup_catalog(self) -> dict[str, Any]:
556
+ catalog = self._catalog(self.repo)
557
+ catalog = json.loads(json.dumps(catalog))
558
+ present = {package["package_id"] for package in catalog["packages"]}
559
+ for package_id, label in (("@local/personal", "Personal corpus"),
560
+ ("@local/learnings-claude", "Claude learning records"),
561
+ ("@local/learnings-codex", "Codex learning records")):
562
+ if package_id not in present:
563
+ catalog["packages"].append({"package_id": package_id, "domains": {"personal": label}})
564
+ return catalog
565
+
566
+ def setup_discover(self, project_roots=None) -> dict[str, Any]:
567
+ try:
568
+ from .corpus_import import discover
569
+ except ImportError:
570
+ from corpus_import import discover
571
+ return discover(environ=self.env, project_roots=project_roots)
572
+
573
+ def setup_revision(self) -> str:
574
+ """Bind an installation review to the current private selection and authoring."""
575
+ paths = (self.record_path, self.runtime / "state.json", self.user_root / "state.json")
576
+ return hashlib.sha256(_canonical({str(path): self._file_version(path) for path in paths})).hexdigest()
577
+
578
+ def _app_manager(self):
579
+ try:
580
+ from .corpus_app import AppBridge
581
+ except ImportError:
582
+ from corpus_app import AppBridge
583
+ return AppBridge(self.repo, self.env)
584
+
585
+ def _app_lifecycle(self, action: str, dry_run: bool = False) -> dict[str, Any]:
586
+ try:
587
+ manager = self._app_manager()
588
+ if action == "remove":
589
+ return (manager.unregister(dry_run) if manager.has_owned_registration()
590
+ else manager.managed_status())
591
+ return manager.refresh_registration()
592
+ except (OSError, RuntimeError) as exc:
593
+ return {"changed": False, "needs_action": [str(exc)]}
594
+
595
+ def setup_extras(self, plan: dict[str, Any], dry_run: bool = False) -> dict[str, Any]:
596
+ try:
597
+ from .corpus_import import capture
598
+ except ImportError:
599
+ from corpus_import import capture
600
+ result: dict[str, Any] = {}
601
+ if plan.get("app_bridge"):
602
+ bridge = self._app_manager()
603
+ status = bridge.status()
604
+ if status.get("needs_action"):
605
+ raise InstallError("; ".join(status["needs_action"]))
606
+ result["app_bridge"] = ({"requested": True, "dry_run": True} if dry_run
607
+ else bridge.register())
608
+ paths = plan.get("import_paths") or []
609
+ if paths:
610
+ discovered = self.setup_discover(plan.get("project_roots"))
611
+ allowed = {row["path"] for row in discovered["sources"]}
612
+ if not set(paths) <= allowed:
613
+ raise InstallError("instruction import selection is not in the reviewed discovery set")
614
+ if dry_run:
615
+ result["import"] = {"paths": paths, "classification": "requires model review", "dry_run": True}
616
+ else:
617
+ record = self._old_record()
618
+ if record is None:
619
+ raise InstallError("install the private runtime before capturing instructions")
620
+ try:
621
+ captured = capture(self._store(Path(record["package_root"])), paths,
622
+ environ=self.env, project_roots=plan.get("project_roots"),
623
+ expected_source_digests=plan.get("_expected_source_digests"))
624
+ except (OSError, RuntimeError, ValueError) as exc:
625
+ failure = InstallError(str(exc))
626
+ failure.completed_extras = result
627
+ raise failure from exc
628
+ result["import"] = {"capture_id": captured["capture_id"], "source_count": len(captured["sources"]),
629
+ "classification": "requires model review",
630
+ "next_command": "agent-bios import prompt " + captured["capture_id"],
631
+ "app_request": "Use $agent-bios to import capture " + captured["capture_id"]}
632
+ return result
633
+
547
634
  def _shell_paths(self, action: str) -> list[dict[str, Any]]:
548
635
  try:
549
636
  manager = self._shell_manager()
@@ -561,14 +648,45 @@ class CorpusInstaller:
561
648
  for row in changes]
562
649
 
563
650
  @_serialized
564
- def install(self, domains: str | None = None, dry_run: bool = False) -> dict[str, Any]:
651
+ def install(self, domains: str | None = None, dry_run: bool = False, *,
652
+ selection_mode: str | None = None, targets: list[str] | None = None) -> dict[str, Any]:
565
653
  files = self._package_files()
566
- requested = self._normalized_domains(domains)
654
+ explicit_selection = domains is not None or selection_mode is not None or targets is not None
655
+ if domains is not None and selection_mode is None:
656
+ selection_mode = "default"
657
+ if selection_mode not in {None, "default", "selected", "none"}:
658
+ raise InstallError("selection_mode must be default, selected, or none")
659
+ if targets is not None and (not isinstance(targets, list) or not all(isinstance(x, str) for x in targets)):
660
+ raise InstallError("corpus targets must be a list of qualified names")
661
+ if domains is not None and targets is not None:
662
+ raise InstallError("use domain flags or corpus targets, not both")
663
+ if domains is not None and selection_mode == "none":
664
+ raise InstallError("no-corpus installation cannot include domain flags")
665
+ requested = list(targets) if targets is not None else ([] if selection_mode == "none" else self._normalized_domains(domains))
666
+ if selection_mode == "none" and requested:
667
+ raise InstallError("no-corpus installation cannot include targets")
668
+ if selection_mode == "selected" and not requested:
669
+ raise InstallError("selected corpus installation needs at least one target")
567
670
  prior = self._old_record()
568
671
  # An update without an explicit selection must not broaden a saved
569
672
  # core-only or domain-limited environment back to every domain.
570
- if domains is None and isinstance((prior or {}).get("selection"), list):
673
+ if domains is None and not explicit_selection and isinstance((prior or {}).get("selection"), list):
571
674
  requested = prior["selection"]
675
+ selection_mode = prior.get("selection_mode")
676
+ if selection_mode is not None:
677
+ preview_store = self._store(self.repo)
678
+ catalog = self.setup_catalog()
679
+ try:
680
+ available = list(catalog["items"])
681
+ runtime = preview_store._runtime_state()
682
+ if runtime.get("selected_baseline_ref"):
683
+ user = preview_store._user_state()
684
+ for host in ("claude", "codex"):
685
+ available.extend(item for item in preview_store._effective_items(runtime, user, host=host)[0]
686
+ if item["package_id"].startswith("@local/") and item.get("active", True))
687
+ preview_store._validate_snapshot_selection(requested, catalog, available)
688
+ except (ValueError, RuntimeError) as exc:
689
+ raise InstallError(str(exc)) from exc
572
690
  # A preview is strictly read-only. A real install first refuses an
573
691
  # unrelated reset, then finishes only its own pending install before
574
692
  # creating an immutable release directory.
@@ -581,7 +699,8 @@ class CorpusInstaller:
581
699
  release, entries, digest = self._copy_release(files, dry_run)
582
700
  if dry_run:
583
701
  return {"dry_run": True, "release": str(release), "release_digest": digest,
584
- "files": len(entries), "selection": requested}
702
+ "files": len(entries), "selection": requested, "selection_mode": selection_mode or "default",
703
+ "corpus_storage": "packaged library retained privately; selection controls delivery"}
585
704
  shell_paths = self._shell_paths("restore")
586
705
  # Stage the immutable baseline before publishing either source pointers
587
706
  # or launcher projections. Store owns those source pointers; this
@@ -590,7 +709,8 @@ class CorpusInstaller:
590
709
  prepare = getattr(store, "prepare_install", None)
591
710
  if not callable(prepare):
592
711
  raise InstallError("private corpus store does not support staged install recovery")
593
- candidate = prepare(requested)
712
+ candidate = (prepare(requested, selection_mode=selection_mode, replace_selection=explicit_selection)
713
+ if selection_mode is not None else prepare(requested))
594
714
  if not isinstance(candidate, dict) or not isinstance(candidate.get("details"), dict):
595
715
  raise InstallError("private corpus store returned an invalid install candidate")
596
716
  details = candidate["details"]
@@ -621,6 +741,8 @@ class CorpusInstaller:
621
741
  "launcher": launcher, "config_files": config, "created_at": int(time.time()),
622
742
  "mode": "private-session-scoped", "needs_action": [],
623
743
  }
744
+ if selection_mode is not None:
745
+ record["selection_mode"] = selection_mode
624
746
  paths = [{"path": str(path), "before": self._file_version(path),
625
747
  "after": self._planned_version(content), "mode": mode}
626
748
  for path, content, mode in owned]
@@ -649,7 +771,8 @@ class CorpusInstaller:
649
771
  try:
650
772
  with operation_scope(self.state_root):
651
773
  self._finish_install_transaction(journal, journal_data, store, candidate)
652
- return {"dry_run": False, "stored": True, "activation": "unverified", "record": record}
774
+ return {"dry_run": False, "stored": True, "activation": "unverified", "record": record,
775
+ "app_bridge": self._app_lifecycle("refresh")}
653
776
  except BaseException as exc:
654
777
  journal_data["state"] = "NEEDS_RECOVERY"
655
778
  journal_data["error"] = type(exc).__name__
@@ -813,7 +936,8 @@ class CorpusInstaller:
813
936
  except (OSError, RuntimeError) as exc:
814
937
  raise InstallError(str(exc)) from exc
815
938
  if record is None:
816
- return {"removed": shell_removed, "preserved": ["no private install record"]}
939
+ app = self._app_lifecycle("remove", dry_run)
940
+ return {"removed": shell_removed, "preserved": ["no private install record"], "app_bridge": app}
817
941
  removed: list[str] = list(shell_removed)
818
942
  preserved: list[str] = []
819
943
  for entry in [record.get("launcher"), *(record.get("config_files") or [])]:
@@ -840,7 +964,8 @@ class CorpusInstaller:
840
964
  self.record_path.unlink(missing_ok=True)
841
965
  # User package/overlays/learnings and session snapshots/pins are purposely
842
966
  # not enumerated or deleted here. No whole state-root removal occurs.
843
- return {"removed": removed, "preserved": preserved,
967
+ app = self._app_lifecycle("remove", dry_run)
968
+ return {"removed": removed, "preserved": preserved, "app_bridge": app,
844
969
  "retained": [str(self.user_root), str(self.state_root / "sessions")]}
845
970
 
846
971
  def _reset_layout(self, record: dict[str, Any], release: Path) -> tuple[list[Path], Path, Path, list[tuple[Path, bytes, int]]]:
@@ -1617,7 +1742,7 @@ class CorpusInstaller:
1617
1742
 
1618
1743
 
1619
1744
  def main(argv: list[str] | None = None) -> int:
1620
- parser = argparse.ArgumentParser(prog="agent-bios corpus")
1745
+ parser = argparse.ArgumentParser(prog="agent-bios")
1621
1746
  parser.add_argument("--repo", help=argparse.SUPPRESS)
1622
1747
  subparsers = parser.add_subparsers(dest="command", required=True)
1623
1748
  install = subparsers.add_parser("install")
@@ -1628,6 +1753,14 @@ def main(argv: list[str] | None = None) -> int:
1628
1753
  onboard.add_argument("--domains")
1629
1754
  onboard.add_argument("--dry-run", action="store_true")
1630
1755
  onboard.add_argument("--with", dest="with_capabilities")
1756
+ for command in (install, onboard):
1757
+ interaction = command.add_mutually_exclusive_group()
1758
+ interaction.add_argument("--interactive", action="store_true", help="open the Textual installation wizard (default)")
1759
+ interaction.add_argument("--non-interactive", action="store_true", help="run without the wizard and return JSON")
1760
+ command.add_argument("--corpus", choices=("all", "none", "selected"),
1761
+ help="active corpus policy; none retains library assets privately but delivers no corpus")
1762
+ command.add_argument("--select", action="append", default=[], metavar="TARGET",
1763
+ help="package/domain/item target; repeat for multiple targets")
1631
1764
  subparsers.add_parser("verify")
1632
1765
  subparsers.add_parser("status")
1633
1766
  uninstall = subparsers.add_parser("uninstall")
@@ -1643,11 +1776,41 @@ def main(argv: list[str] | None = None) -> int:
1643
1776
  reset.add_argument("--expected-revision", help="reset preview generation to accept")
1644
1777
  args = parser.parse_args(argv)
1645
1778
  installer = CorpusInstaller(Path(args.repo) if args.repo else Path(__file__).resolve().parent.parent)
1779
+ interactive = False
1646
1780
  try:
1647
1781
  if args.command in {"install", "onboard"}:
1648
1782
  if args.with_capabilities:
1649
- raise InstallError("optional --with dependencies are not installed by private corpus mode")
1650
- result = installer.install(args.domains, args.dry_run)
1783
+ raise InstallError("use --interactive to select dependency installation; --with is legacy-only")
1784
+ interactive = not args.non_interactive
1785
+ if args.select and args.corpus != "selected":
1786
+ raise InstallError("--select requires --corpus selected")
1787
+ if interactive and args.domains is not None:
1788
+ raise InstallError("--domains is a compatibility selection flag; use --non-interactive or choose corpus in the wizard")
1789
+ if interactive:
1790
+ if not sys.stdin.isatty() or not sys.stdout.isatty():
1791
+ raise InstallError("interactive installation needs an input/output terminal; use --non-interactive for automation or app tool calls")
1792
+ if os.environ.get("TERM") == "dumb":
1793
+ raise InstallError("this terminal cannot display the Textual wizard; use a capable terminal or --non-interactive")
1794
+ try:
1795
+ from .corpus_ui_runtime import activate_ui_runtime
1796
+ except ImportError:
1797
+ from corpus_ui_runtime import activate_ui_runtime
1798
+ activate_ui_runtime(installer.repo)
1799
+ try:
1800
+ from .corpus_setup_ui import run_setup_ui
1801
+ except ImportError:
1802
+ from corpus_setup_ui import run_setup_ui
1803
+ initial = None
1804
+ if args.corpus is not None:
1805
+ mode = "none" if args.corpus == "none" else "selected"
1806
+ targets = ["all"] if args.corpus == "all" else list(args.select) if args.corpus == "selected" else []
1807
+ initial = {"selection_mode": mode, "targets": targets, "dependencies": [],
1808
+ "app_bridge": False, "import_paths": [], "project_roots": []}
1809
+ result = run_setup_ui(installer, dry_run=args.dry_run, initial_plan=initial)
1810
+ else:
1811
+ mode = "none" if args.corpus == "none" else "selected" if args.corpus else None
1812
+ targets = ["all"] if args.corpus == "all" else args.select if args.corpus == "selected" else None
1813
+ result = installer.install(args.domains, args.dry_run, selection_mode=mode, targets=targets)
1651
1814
  elif args.command == "verify":
1652
1815
  result = installer.verify()
1653
1816
  elif args.command == "status":
@@ -1659,10 +1822,25 @@ def main(argv: list[str] | None = None) -> int:
1659
1822
  expected_revision=args.expected_revision)
1660
1823
  else:
1661
1824
  result = installer.migrate(apply=args.apply and not args.dry_run, yes=args.yes)
1662
- print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
1663
- return 0
1664
- except InstallError as exc:
1665
- print(f"corpus-install: {exc}", file=sys.stderr)
1825
+ if interactive:
1826
+ try:
1827
+ from .corpus_setup import format_setup_result
1828
+ except ImportError:
1829
+ from corpus_setup import format_setup_result
1830
+ print(format_setup_result(result, language=result.get("ui_language", "en")))
1831
+ else:
1832
+ print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
1833
+ return 1 if result.get("dependency_failed") or result.get("extras_error") or result.get("installation_error") or result.get("error") else 0
1834
+ except (InstallError, RuntimeError, ValueError, OSError) as exc:
1835
+ message = str(exc)
1836
+ language = getattr(exc, "ui_language", "en")
1837
+ if interactive and language != "en":
1838
+ try:
1839
+ from .corpus_setup_i18n import translate
1840
+ except ImportError:
1841
+ from corpus_setup_i18n import translate
1842
+ message = translate(language, "Setup could not finish. Details: {detail}", detail=message)
1843
+ print(f"corpus-install: {message}", file=sys.stderr)
1666
1844
  return 1
1667
1845
 
1668
1846
 
@@ -23,6 +23,23 @@ class SessionError(RuntimeError):
23
23
  pass
24
24
 
25
25
 
26
+ def validate_working_directory_argv(host, argv):
27
+ """Keep a private Codex snapshot and its native session in the same directory."""
28
+ if host != 'codex':
29
+ return
30
+ if not isinstance(argv, (list, tuple)) or not all(isinstance(token, str) for token in argv):
31
+ raise SessionError('private Codex activation requires a valid argument list')
32
+ for token in argv:
33
+ if token == '--':
34
+ break
35
+ if token == '--cd' or token.startswith('--cd=') or token.startswith('-C'):
36
+ raise SessionError(
37
+ 'private Codex corpus activation cannot include --cd/-C: the selected corpus and '
38
+ 'session pin use the launch working directory. cd into the target directory first, '
39
+ 'then start a new activated session without a working-directory override'
40
+ )
41
+
42
+
26
43
  def _global_instruction_choice(host, include_global_instructions):
27
44
  if type(include_global_instructions) is not bool:
28
45
  raise SessionError('include_global_instructions must be a boolean')
@@ -104,6 +121,7 @@ def _settings_values(argv):
104
121
 
105
122
 
106
123
  def _record_instruction_choice(record, env, check_paths=True):
124
+ validate_working_directory_argv(record['host'], record.get('argv'))
107
125
  include = _global_instruction_choice(record['host'], record.get('include_global_instructions', True))
108
126
  if not include:
109
127
  values = _settings_values(record['argv'])
@@ -455,6 +473,7 @@ def _verified_launch_snapshot(state_root, snapshot):
455
473
 
456
474
  def compose_argv(command, argv, host, snapshot, cwd=None, env=None, include_global_instructions=True):
457
475
  """Preserve native instructions before the selected corpus and launch contract."""
476
+ validate_working_directory_argv(host, argv)
458
477
  _global_instruction_choice(host, include_global_instructions)
459
478
  native_env = dict(os.environ if env is None else env)
460
479
  native_cwd = pathlib.Path(cwd or pathlib.Path.cwd()).resolve()
@@ -733,6 +752,7 @@ def recover_activations(state_root, command=None, host=None, env=None):
733
752
 
734
753
 
735
754
  def create_codex_session(command, argv, state_root, record, cwd, env):
755
+ validate_working_directory_argv('codex', argv)
736
756
  with CodexServer(command, config_flags(argv), cwd, env) as server:
737
757
  params = {'cwd': str(cwd), 'developerInstructions': instruction_value(argv, 'codex'),
738
758
  'ephemeral': False, 'experimentalRawEvents': False}
@@ -763,12 +783,19 @@ def create_codex_session(command, argv, state_root, record, cwd, env):
763
783
  def launch(command, argv, state_root, host, snapshot, cwd=None, env=None, resume_id=None,
764
784
  include_global_instructions=True):
765
785
  """Start a pinned native session, returning its exit status."""
786
+ validate_working_directory_argv(host, argv)
766
787
  cwd = pathlib.Path(cwd or pathlib.Path.cwd()).resolve()
767
788
  env = dict(os.environ if env is None else env)
768
789
  if resume_id and include_global_instructions is not True:
769
790
  raise SessionError('resume uses its recorded global instruction choice; omit the override or start a new session')
770
791
  if not resume_id:
771
792
  _global_instruction_choice(host, include_global_instructions)
793
+ elif host == 'codex':
794
+ _, pins = session_paths(state_root)
795
+ session_id = validate_session_id(resume_id)
796
+ pin_path = pins / host / f'{session_id}.json'
797
+ if pin_path.exists() or pin_path.is_symlink():
798
+ read_pin(state_root, host, session_id)
772
799
  recovered = recover_activations(state_root, command, host, env)
773
800
  if recovered['pending']:
774
801
  print(f"agent-bios: {len(recovered['pending'])} activation(s) lack host evidence; their snapshots remain retained.", file=sys.stderr)