agent-bios 0.19.1 → 0.19.2

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 (85) hide show
  1. package/DEPENDENCIES.md +27 -27
  2. package/INSTALL.md +4 -4
  3. package/README.md +53 -26
  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 +3 -3
  7. package/claude/guides/documentation-hygiene.md +3 -0
  8. package/claude/guides/gpt-prompting.md +1 -1
  9. package/claude/guides/korean-writing.md +153 -0
  10. package/claude/guides/learning-flow.md +4 -4
  11. package/claude/guides/session-distill-workflow.md +8 -8
  12. package/claude/guides/slide-writing/RUNBOOK.md +5 -5
  13. package/claude/skills/repo-charter/SKILL.md +3 -3
  14. package/claude/skills/understand/SKILL.md +5 -5
  15. package/codex/AGENTS.md +1 -1
  16. package/codex/guides/claude-prompting.md +1 -1
  17. package/codex/guides/cli-multi-model-workflow.md +3 -3
  18. package/codex/guides/documentation-hygiene.md +3 -0
  19. package/codex/guides/gpt-prompting.md +1 -1
  20. package/codex/guides/korean-writing.md +153 -0
  21. package/codex/guides/learning-flow.md +4 -4
  22. package/codex/guides/session-distill-workflow.md +8 -8
  23. package/codex/guides/slide-writing/RUNBOOK.md +5 -5
  24. package/compose/app_bridge/SKILL.md +12 -12
  25. package/compose/app_bridge/scripts/bridge.py +15 -7
  26. package/compose/assemble.py +5 -5
  27. package/compose/bootstrap/SKILL.md +18 -18
  28. package/compose/canary.sh +4 -4
  29. package/compose/check-domains.py +6 -6
  30. package/compose/corpus-state.py +16 -1168
  31. package/compose/corpus.py +13 -402
  32. package/compose/corpus_app.py +14 -450
  33. package/compose/corpus_catalog.py +15 -926
  34. package/compose/corpus_import.py +14 -523
  35. package/compose/corpus_install.py +14 -1847
  36. package/compose/corpus_session.py +16 -848
  37. package/compose/corpus_setup.py +16 -672
  38. package/compose/corpus_setup_cli.py +15 -580
  39. package/compose/corpus_setup_i18n.py +20 -324
  40. package/compose/corpus_setup_ui.py +18 -645
  41. package/compose/corpus_store.py +16 -1664
  42. package/compose/corpus_transaction.py +15 -284
  43. package/compose/corpus_ui.py +17 -972
  44. package/compose/corpus_ui_runtime.py +16 -274
  45. package/compose/corpus_understand.py +13 -671
  46. package/compose/domains.json +2 -1
  47. package/compose/instructions-state.py +1175 -0
  48. package/compose/instructions.py +409 -0
  49. package/compose/instructions_app.py +464 -0
  50. package/compose/instructions_catalog.py +931 -0
  51. package/compose/instructions_import.py +529 -0
  52. package/compose/instructions_install.py +1866 -0
  53. package/compose/instructions_session.py +852 -0
  54. package/compose/instructions_setup.py +676 -0
  55. package/compose/instructions_setup_cli.py +586 -0
  56. package/compose/instructions_setup_i18n.py +324 -0
  57. package/compose/instructions_setup_ui.py +647 -0
  58. package/compose/instructions_store.py +1668 -0
  59. package/compose/instructions_transaction.py +306 -0
  60. package/compose/instructions_ui.py +975 -0
  61. package/compose/instructions_ui_runtime.py +278 -0
  62. package/compose/instructions_understand.py +678 -0
  63. package/compose/register-hooks.py +1 -1
  64. package/compose/setup/START.md +11 -11
  65. package/docs/advanced-launch.md +11 -11
  66. package/docs/instructions-compatibility.md +86 -0
  67. package/docs/{corpus.md → instructions.md} +35 -8
  68. package/docs/recovery.md +10 -10
  69. package/docs/releases/0.19.2.md +38 -0
  70. package/docs/session-model.md +23 -20
  71. package/docs/setup.md +27 -26
  72. package/docs/understand.md +6 -6
  73. package/install.sh +70 -69
  74. package/launch/agent-launch.py +298 -290
  75. package/launch/agent-launch.toml +2 -2
  76. package/launch/i18n/en.toml +55 -55
  77. package/launch/i18n/ja.toml +56 -56
  78. package/launch/i18n/ko.toml +56 -56
  79. package/launch/shell_integration.py +4 -4
  80. package/learn/collect-learning.py +10 -10
  81. package/learn/learning.schema.json +1 -1
  82. package/learn/migrate-learnings.py +51 -51
  83. package/package.json +27 -11
  84. package/provenance.json +1 -1
  85. /package/docs/assets/{corpus-studio.svg → instructions-studio.svg} +0 -0
@@ -0,0 +1,464 @@
1
+ #!/usr/bin/env python3
2
+ """Explicit Codex app discovery and per-task instructions context delivery."""
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from datetime import datetime, timezone
7
+ import hashlib
8
+ import json
9
+ import os
10
+ from pathlib import Path
11
+ import re
12
+ import shutil
13
+ import sys
14
+ import tempfile
15
+ from typing import Any
16
+
17
+ try:
18
+ from instructions_store import InstructionsStore, InstructionsStoreError
19
+ from instructions_transaction import (environment_value, TransactionError, confirmed_release, guard_pending,
20
+ reject_symlink_ancestors, transaction_lock)
21
+ except ImportError:
22
+ from .instructions_store import InstructionsStore, InstructionsStoreError
23
+ from .instructions_transaction import (environment_value, TransactionError, confirmed_release, guard_pending,
24
+ reject_symlink_ancestors, transaction_lock)
25
+
26
+
27
+ SCHEMA_VERSION = 1
28
+ BRIDGE_MEMBERS = ("SKILL.md", "agents/openai.yaml", "scripts/bridge.py",
29
+ "scripts/instructions_transaction.py", "bridge.json")
30
+ LEGACY_BRIDGE_MEMBERS = ("SKILL.md", "agents/openai.yaml", "scripts/bridge.py",
31
+ "scripts/corpus_transaction.py", "bridge.json")
32
+ SESSION_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9_-]{0,127}\Z")
33
+
34
+
35
+ class AppError(RuntimeError):
36
+ pass
37
+
38
+
39
+ def _json_bytes(value: Any) -> bytes:
40
+ return (json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n").encode("utf-8")
41
+
42
+
43
+ def _read_json(path: Path) -> dict[str, Any]:
44
+ reject_symlink_ancestors(path)
45
+ try:
46
+ value = json.loads(path.read_text(encoding="utf-8"))
47
+ except (OSError, ValueError) as exc:
48
+ raise AppError(f"unreadable app state: {path}") from exc
49
+ if not isinstance(value, dict):
50
+ raise AppError(f"invalid app state: {path}")
51
+ return value
52
+
53
+
54
+ def _write_json(path: Path, value: dict[str, Any]) -> None:
55
+ reject_symlink_ancestors(path)
56
+ path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
57
+ descriptor, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
58
+ try:
59
+ with os.fdopen(descriptor, "wb") as handle:
60
+ handle.write(_json_bytes(value))
61
+ handle.flush()
62
+ os.fsync(handle.fileno())
63
+ os.replace(temporary, path)
64
+ finally:
65
+ if os.path.lexists(temporary):
66
+ os.unlink(temporary)
67
+
68
+
69
+ def _tree_digest(members: dict[str, bytes]) -> str:
70
+ digest = hashlib.sha256()
71
+ for relative, data in sorted(members.items()):
72
+ digest.update(relative.encode("utf-8") + b"\0" + data + b"\0")
73
+ return digest.hexdigest()
74
+
75
+
76
+ def _roots(environ: dict[str, str] | None) -> tuple[dict[str, str], Path, Path, Path]:
77
+ env = dict(os.environ if environ is None else environ)
78
+ home = Path(env.get("HOME", str(Path.home()))).expanduser().absolute()
79
+ state = Path(env.get("AGENT_BIOS_STATE_DIR", str(home / ".local/share/agent-bios"))).expanduser().absolute()
80
+ user = Path(environment_value(env, "AGENT_BIOS_INSTRUCTIONS_DIR", "AGENT_BIOS_CORPUS_DIR", str(home / ".config/agent-bios/corpus"))).expanduser().absolute()
81
+ return env, home, state, user
82
+
83
+
84
+ class AppBridge:
85
+ """Own one explicit-only skill symlink; retain immutable private generations."""
86
+
87
+ def __init__(self, repo: Path, environ: dict[str, str] | None = None):
88
+ self.repo = Path(repo).absolute()
89
+ self.env, self.home, self.state_root, self.user_root = _roots(environ)
90
+ self.target = self.home / ".agents/skills/agent-bios"
91
+ self.generations = self.state_root / "runtime/app-bridges"
92
+
93
+ def _base_config(self) -> dict[str, Any]:
94
+ return {"schema_version": SCHEMA_VERSION, "home": str(self.home),
95
+ "state_root": str(self.state_root), "user_root": str(self.user_root),
96
+ "discovery_path": str(self.target)}
97
+
98
+ def _config(self) -> dict[str, Any]:
99
+ config = self._base_config()
100
+ if "AGENT_LAUNCH_VENV" in self.env:
101
+ value = self.env["AGENT_LAUNCH_VENV"]
102
+ config["launch_venv"] = str(Path(value).expanduser().absolute()) if value else ""
103
+ else:
104
+ current = self._owned_target()
105
+ if current is not None:
106
+ saved = _read_json(current / "bridge.json")
107
+ if "launch_venv" in saved:
108
+ config["launch_venv"] = saved["launch_venv"]
109
+ return config
110
+
111
+ def _source_members(self) -> dict[str, bytes]:
112
+ reject_symlink_ancestors(self.state_root)
113
+ guard_pending(self.state_root)
114
+ release = confirmed_release(self.state_root)
115
+ source = release / "compose/app_bridge"
116
+ members = {}
117
+ canonical = (release / "compose/instructions_transaction.py").is_file()
118
+ inventory = BRIDGE_MEMBERS if canonical else LEGACY_BRIDGE_MEMBERS
119
+ for name in inventory:
120
+ if name == "bridge.json":
121
+ members[name] = _json_bytes(self._config())
122
+ continue
123
+ path = release / "compose" / Path(name).name if name in {
124
+ "scripts/instructions_transaction.py", "scripts/corpus_transaction.py"
125
+ } else source / name
126
+ reject_symlink_ancestors(path)
127
+ if not path.is_file():
128
+ raise AppError(f"installed release has no app bridge member: {path}")
129
+ members[name] = path.read_bytes()
130
+ return members
131
+
132
+ def _owned_target(self) -> Path | None:
133
+ reject_symlink_ancestors(self.target.parent)
134
+ if not os.path.lexists(self.target):
135
+ return None
136
+ if not self.target.is_symlink():
137
+ raise AppError(f"preserving unowned app skill: {self.target}")
138
+ raw = Path(os.readlink(self.target))
139
+ if not raw.is_absolute() or raw.parent != self.generations or not re.fullmatch(r"[a-f0-9]{64}", raw.name):
140
+ raise AppError(f"preserving unowned app skill link: {self.target}")
141
+ reject_symlink_ancestors(raw)
142
+ if not raw.is_dir():
143
+ raise AppError(f"owned app skill generation is unavailable: {raw}")
144
+ paths = list(raw.rglob("*"))
145
+ if any(path.is_symlink() for path in paths):
146
+ raise AppError(f"preserving redirected app skill generation: {raw}")
147
+ files = {path.relative_to(raw).as_posix(): path.read_bytes() for path in paths if path.is_file()}
148
+ if set(files) not in (set(BRIDGE_MEMBERS), set(LEGACY_BRIDGE_MEMBERS)) or _tree_digest(files) != raw.name:
149
+ raise AppError(f"preserving changed app skill generation: {raw}")
150
+ config = _read_json(raw / "bridge.json")
151
+ base = self._base_config()
152
+ if (any(config.get(key) != value for key, value in base.items())
153
+ or set(config) - set(base) - {"launch_venv"}
154
+ or ("launch_venv" in config and (not isinstance(config["launch_venv"], str)
155
+ or (config["launch_venv"] and not Path(config["launch_venv"]).is_absolute())))):
156
+ raise AppError(f"app skill belongs to different private root settings: {self.target}")
157
+ return raw
158
+
159
+ def status(self) -> dict[str, Any]:
160
+ try:
161
+ target = self._owned_target()
162
+ return {"registered": target is not None, "discovery_path": str(self.target),
163
+ "generation": str(target) if target else None, "implicit_invocation": False,
164
+ "native_discovery": "unverified", "session_activation": "explicit-use-only"}
165
+ except (AppError, OSError, TransactionError) as exc:
166
+ return {"registered": False, "discovery_path": str(self.target), "needs_action": [str(exc)],
167
+ "native_discovery": "unverified", "session_activation": "explicit-use-only"}
168
+
169
+ def has_owned_registration(self) -> bool:
170
+ """Identify our link namespace without opening unrelated skill contents."""
171
+ try:
172
+ reject_symlink_ancestors(self.target.parent)
173
+ if not self.target.is_symlink():
174
+ return False
175
+ target = Path(os.readlink(self.target))
176
+ return target.is_absolute() and target.parent == self.generations
177
+ except (OSError, TransactionError):
178
+ return False
179
+
180
+ def managed_status(self) -> dict[str, Any]:
181
+ """Lifecycle view: foreign native paths are outside this installer."""
182
+ if not self.has_owned_registration():
183
+ return {"registered": False, "managed": False, "discovery_path": str(self.target)}
184
+ return {**self.status(), "managed": True}
185
+
186
+ def register(self, dry_run: bool = False) -> dict[str, Any]:
187
+ reject_symlink_ancestors(self.state_root)
188
+ if dry_run:
189
+ before = self._owned_target()
190
+ members = self._source_members()
191
+ generation = self.generations / _tree_digest(members)
192
+ return {"registered": before is not None, "dry_run": True, "changed": before != generation,
193
+ "discovery_path": str(self.target), "generation": str(generation),
194
+ "implicit_invocation": False, "native_discovery": "unverified",
195
+ "session_activation": "explicit-use-only"}
196
+ with transaction_lock(self.state_root):
197
+ before = self._owned_target()
198
+ members = self._source_members()
199
+ generation = self.generations / _tree_digest(members)
200
+ result = {"registered": True, "dry_run": False, "changed": before != generation,
201
+ "discovery_path": str(self.target), "generation": str(generation),
202
+ "implicit_invocation": False, "native_discovery": "unverified",
203
+ "session_activation": "explicit-use-only"}
204
+ reject_symlink_ancestors(generation)
205
+ if generation.exists():
206
+ paths = list(generation.rglob("*"))
207
+ actual = {path.relative_to(generation).as_posix(): path.read_bytes()
208
+ for path in paths if path.is_file() and not path.is_symlink()}
209
+ if any(path.is_symlink() for path in paths) or actual != members:
210
+ raise AppError(f"app bridge generation collision: {generation}")
211
+ else:
212
+ self.generations.mkdir(parents=True, exist_ok=True, mode=0o700)
213
+ temporary = Path(tempfile.mkdtemp(prefix=".bridge-", dir=self.generations))
214
+ try:
215
+ for relative, data in members.items():
216
+ path = temporary / relative
217
+ path.parent.mkdir(parents=True, exist_ok=True)
218
+ path.write_bytes(data)
219
+ path.chmod(0o600)
220
+ os.replace(temporary, generation)
221
+ finally:
222
+ if temporary.exists():
223
+ shutil.rmtree(temporary)
224
+ self.target.parent.mkdir(parents=True, exist_ok=True)
225
+ if self._owned_target() != before:
226
+ raise AppError("app skill changed while preparing registration")
227
+ descriptor, temporary = tempfile.mkstemp(prefix=".agent-bios-link-", dir=self.target.parent)
228
+ os.close(descriptor)
229
+ os.unlink(temporary)
230
+ try:
231
+ os.symlink(str(generation), temporary)
232
+ os.replace(temporary, self.target)
233
+ finally:
234
+ if os.path.lexists(temporary):
235
+ os.unlink(temporary)
236
+ return result
237
+
238
+ def unregister(self, dry_run: bool = False) -> dict[str, Any]:
239
+ reject_symlink_ancestors(self.state_root)
240
+ if dry_run or not os.path.lexists(self.target):
241
+ before = self._owned_target()
242
+ return {"registered": before is not None, "dry_run": dry_run, "changed": before is not None,
243
+ "discovery_path": str(self.target), "retained_private_generations": True}
244
+ with transaction_lock(self.state_root):
245
+ before = self._owned_target()
246
+ if before is not None:
247
+ self.target.unlink()
248
+ return {"registered": False, "dry_run": False, "changed": before is not None,
249
+ "discovery_path": str(self.target), "retained_private_generations": True}
250
+
251
+ def refresh_registration(self) -> dict[str, Any]:
252
+ """Refresh an existing owned registration; installation never opts in."""
253
+ status = self.managed_status()
254
+ if not status["registered"]:
255
+ return status
256
+ try:
257
+ return {**self.register(), "managed": True}
258
+ except (AppError, OSError, TransactionError) as exc:
259
+ return {**self.managed_status(), "needs_action": [str(exc)]}
260
+
261
+
262
+ class AppSessions:
263
+ """Record returned context, separately from native launch pins and host proof."""
264
+
265
+ def __init__(self, repo: Path, environ: dict[str, str] | None = None):
266
+ self.repo = Path(repo).absolute()
267
+ self.env, self.home, self.state_root, self.user_root = _roots(environ)
268
+
269
+ def _session(self, session: str | None) -> str:
270
+ value = session if session is not None else self.env.get("CODEX_THREAD_ID")
271
+ if not isinstance(value, str) or not SESSION_ID.fullmatch(value):
272
+ raise AppError("app session requires CODEX_THREAD_ID or --session with a valid task id")
273
+ return value
274
+
275
+ def _path(self, session: str) -> Path:
276
+ return self.state_root / "sessions/app-context" / f"{session}.json"
277
+
278
+ def _read(self, session: str) -> dict[str, Any]:
279
+ path = self._path(session)
280
+ reject_symlink_ancestors(path)
281
+ if not path.exists():
282
+ return {"schema_version": SCHEMA_VERSION, "host": "codex", "session_id": session,
283
+ "enabled": False, "deliveries": []}
284
+ record = _read_json(path)
285
+ if (record.get("schema_version") != SCHEMA_VERSION or record.get("host") != "codex"
286
+ or record.get("session_id") != session or not isinstance(record.get("enabled"), bool)
287
+ or not isinstance(record.get("deliveries"), list)):
288
+ raise AppError(f"invalid app session receipt: {path}")
289
+ for delivery in record["deliveries"]:
290
+ if (not isinstance(delivery, dict) or delivery.get("delivery") != "returned-as-context"
291
+ or not isinstance(delivery.get("content_ref"), str)
292
+ or not re.fullmatch(r"[a-f0-9]{64}", delivery["content_ref"])):
293
+ raise AppError(f"invalid app session delivery: {path}")
294
+ return record
295
+
296
+ @staticmethod
297
+ def _view(record: dict[str, Any]) -> dict[str, Any]:
298
+ delivered = bool(record["deliveries"])
299
+ return {**record, "ever_delivered": delivered,
300
+ "active_content_ref": record["deliveries"][-1]["content_ref"] if delivered and record["enabled"] else None,
301
+ "native_activation": False, "host_loading": "unverified",
302
+ "context_retracted": False,
303
+ "clean_exclusion_requires_new_session": delivered and not record["enabled"]}
304
+
305
+ def status(self, session: str | None = None) -> dict[str, Any]:
306
+ return self._view(self._read(self._session(session)))
307
+
308
+ def off(self, session: str | None = None) -> dict[str, Any]:
309
+ session = self._session(session)
310
+ reject_symlink_ancestors(self.state_root)
311
+ with transaction_lock(self.state_root):
312
+ record = self._read(session)
313
+ record["enabled"] = False
314
+ _write_json(self._path(session), record)
315
+ result = self._view(record)
316
+ result["message"] = (
317
+ "Instructions delivery is off. Previously returned text remains in conversation context; "
318
+ "start a new session for clean exclusion."
319
+ if record["deliveries"] else "Instructions delivery is off; this bridge has returned no instructions text to this session."
320
+ )
321
+ return result
322
+
323
+ def _snapshot(self, *, selection: list[str] | None, selection_mode: str | None,
324
+ cwd: Path | None, dry_run: bool) -> dict[str, Any]:
325
+ reject_symlink_ancestors(self.state_root)
326
+ guard_pending(self.state_root)
327
+ release = confirmed_release(self.state_root)
328
+ store = InstructionsStore(release, self.state_root, self.user_root)
329
+ if selection is not None and selection_mode is None:
330
+ selection_mode = "selected"
331
+ return store.snapshot(host="codex", selection=selection, selection_mode=selection_mode,
332
+ cwd=Path(cwd or Path.cwd()).absolute(), dry_run=dry_run, native=False)
333
+
334
+ def _runtime_commands(self) -> dict[str, Any]:
335
+ release = confirmed_release(self.state_root)
336
+ bridge = AppBridge(self.repo, self.env)
337
+ result = {"package_root": str(release),
338
+ "learn_argv": ["/bin/bash", str(release / "install.sh"), "learn"],
339
+ "environment": {"AGENT_BIOS_PACKAGE_ROOT": str(release),
340
+ "AGENT_BIOS_STATE_DIR": str(self.state_root),
341
+ "AGENT_BIOS_INSTRUCTIONS_DIR": str(self.user_root),
342
+ "AGENT_BIOS_CORPUS_DIR": str(self.user_root),
343
+ "AGENT_BIOS_PRIVATE_CORPUS": "1",
344
+ "AGENT_BIOS_PRIVATE_INSTRUCTIONS": "1", "AGENT_BIOS_LEGACY_INSTALL": "0"}}
345
+ registered = bridge.managed_status().get("registered")
346
+ if "AGENT_LAUNCH_VENV" in self.env or registered:
347
+ config = bridge._config()
348
+ if "launch_venv" in config:
349
+ result["environment"]["AGENT_LAUNCH_VENV"] = config["launch_venv"]
350
+ if registered:
351
+ result["bridge_learn_argv"] = [sys.executable, str(bridge.target / "scripts/bridge.py"), "learn"]
352
+ return result
353
+
354
+ def preview(self, session: str | None = None, *, selection: list[str] | None = None,
355
+ selection_mode: str | None = None, cwd: Path | None = None) -> dict[str, Any]:
356
+ session = self._session(session)
357
+ if selection_mode == "none":
358
+ return {"session_id": session, "selection_mode": "none", "content_ref": None,
359
+ "instruction_characters": 0, "delivery": "preview-only", "native_activation": False}
360
+ snapshot = self._snapshot(selection=selection, selection_mode=selection_mode, cwd=cwd, dry_run=True)
361
+ return {"session_id": session, "content_ref": snapshot["content_ref"], "revision": snapshot["revision"],
362
+ "instruction_characters": len(snapshot["instruction_text"]),
363
+ "unavailable": snapshot.get("unavailable", []), "selection": selection,
364
+ "selection_mode": snapshot.get("selection_mode", selection_mode),
365
+ "delivery": "preview-only", "native_activation": False}
366
+
367
+ def use(self, session: str | None = None, *, selection: list[str] | None = None,
368
+ selection_mode: str | None = None, cwd: Path | None = None,
369
+ expected_content_ref: str | None = None) -> dict[str, Any]:
370
+ session = self._session(session)
371
+ if selection_mode == "none":
372
+ return self.off(session)
373
+ reject_symlink_ancestors(self.state_root)
374
+ with transaction_lock(self.state_root):
375
+ record = self._read(session)
376
+ snapshot = self._snapshot(selection=selection, selection_mode=selection_mode, cwd=cwd, dry_run=False)
377
+ if expected_content_ref is not None and snapshot["content_ref"] != expected_content_ref:
378
+ raise AppError("instructions preview changed; preview again before using this session selection")
379
+ if not snapshot["instruction_text"].strip():
380
+ return self.off(session)
381
+ delivery = {"at": datetime.now(timezone.utc).isoformat(), "content_ref": snapshot["content_ref"],
382
+ "snapshot_path": snapshot["path"], "revision": snapshot["revision"],
383
+ "cwd": str(Path(cwd or Path.cwd()).absolute()), "delivery": "returned-as-context",
384
+ "instruction_sha256": hashlib.sha256(snapshot["instruction_text"].encode("utf-8")).hexdigest()}
385
+ record["deliveries"].append(delivery)
386
+ record["enabled"] = True
387
+ runtime = self._runtime_commands()
388
+ _write_json(self._path(session), record)
389
+ return {**self._view(record), "delivery": "returned-as-context",
390
+ "content_ref": snapshot["content_ref"], "instruction_text": snapshot["instruction_text"],
391
+ "runtime": runtime,
392
+ "unavailable": snapshot.get("unavailable", []),
393
+ "message": "Returned the selected immutable instructions as task context. This receipt is not proof of model reading or native startup activation."}
394
+
395
+
396
+ def build_parser() -> argparse.ArgumentParser:
397
+ parser = argparse.ArgumentParser(prog="agent-bios app", description=__doc__)
398
+ parser.add_argument("--repo", type=Path, default=Path(__file__).resolve().parents[1])
399
+ parser.add_argument("--state-dir", type=Path)
400
+ parser.add_argument("--user-dir", type=Path)
401
+ parser.add_argument("--json", action="store_true")
402
+ commands = parser.add_subparsers(dest="command", required=True)
403
+ for name in ("register", "unregister", "status"):
404
+ command = commands.add_parser(name)
405
+ if name != "status":
406
+ command.add_argument("--dry-run", action="store_true")
407
+ session = commands.add_parser("session", help="explicitly preview, use or stop instructions delivery in one app task")
408
+ operations = session.add_subparsers(dest="operation", required=True)
409
+ for name in ("preview", "use", "off", "status"):
410
+ command = operations.add_parser(name)
411
+ command.add_argument("--session", help="defaults to CODEX_THREAD_ID")
412
+ if name in {"preview", "use"}:
413
+ selection = command.add_mutually_exclusive_group()
414
+ selection.add_argument("--domains", help="comma-separated qualified instructions selection")
415
+ selection.add_argument("--no-instructions", "--no-corpus", action="store_true")
416
+ command.add_argument("--cwd", type=Path)
417
+ if name == "use":
418
+ command.add_argument("--expected-content-ref", help="require the exact previously previewed snapshot")
419
+ return parser
420
+
421
+
422
+ def main(argv: list[str] | None = None) -> int:
423
+ raw = list(sys.argv[1:] if argv is None else argv)
424
+ as_json = "--json" in raw
425
+ args = build_parser().parse_args([token for token in raw if token != "--json"])
426
+ env = dict(os.environ)
427
+ if args.state_dir is not None:
428
+ env["AGENT_BIOS_STATE_DIR"] = str(args.state_dir)
429
+ if args.user_dir is not None:
430
+ env["AGENT_BIOS_INSTRUCTIONS_DIR"] = str(args.user_dir)
431
+ try:
432
+ if args.command == "session":
433
+ manager = AppSessions(args.repo, env)
434
+ options = {}
435
+ if args.operation in {"preview", "use"}:
436
+ selected = None
437
+ if args.domains is not None:
438
+ selected = [value.strip() for value in args.domains.split(",") if value.strip()]
439
+ if not selected:
440
+ raise AppError("--domains requires a nonempty instructions selection; use --no-instructions to keep instructions off")
441
+ options = {"selection": selected,
442
+ "selection_mode": "none" if args.no_instructions else "selected" if selected is not None else None,
443
+ "cwd": args.cwd}
444
+ if args.operation == "use":
445
+ options["expected_content_ref"] = args.expected_content_ref
446
+ result = getattr(manager, args.operation)(session=args.session, **options)
447
+ else:
448
+ manager = AppBridge(args.repo, env)
449
+ options = {} if args.command == "status" else {"dry_run": args.dry_run}
450
+ result = getattr(manager, args.command)(**options)
451
+ if not as_json and isinstance(result.get("instruction_text"), str):
452
+ metadata = {key: value for key, value in result.items() if key != "instruction_text"}
453
+ print(json.dumps(metadata, ensure_ascii=False, indent=2, sort_keys=True), file=sys.stderr)
454
+ print(result["instruction_text"], end="")
455
+ else:
456
+ print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
457
+ return 0
458
+ except (AppError, InstructionsStoreError, TransactionError, OSError) as exc:
459
+ print(f"agent-bios app: {exc}", file=sys.stderr)
460
+ return 2
461
+
462
+
463
+ if __name__ == "__main__":
464
+ raise SystemExit(main())