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.
- package/DEPENDENCIES.md +58 -30
- package/INSTALL.md +4 -4
- package/README.md +105 -28
- package/claude/CLAUDE.md +1 -1
- package/claude/guides/claude-prompting.md +1 -1
- package/claude/guides/cli-multi-model-workflow.md +4 -4
- package/claude/guides/coding-staged-workflow.md +17 -0
- package/claude/guides/documentation-hygiene.md +3 -0
- package/claude/guides/gpt-prompting.md +1 -1
- package/claude/guides/korean-writing.md +153 -0
- package/claude/guides/learning-flow.md +4 -4
- package/claude/guides/llm-capability-boundary.md +7 -1
- package/claude/guides/session-distill-workflow.md +8 -8
- package/claude/guides/slide-writing/RUNBOOK.md +5 -5
- package/claude/guides/tooling-gotchas.md +20 -1
- package/claude/guides/ui-design/visual-direction.md +88 -0
- package/claude/guides/ui-design.md +90 -0
- package/claude/guides/verification-discipline.md +10 -1
- package/claude/hooks/tooling-gotchas-hook.py +41 -0
- package/claude/skills/repo-charter/SKILL.md +3 -3
- package/claude/skills/understand/SKILL.md +5 -5
- package/codex/AGENTS.md +1 -1
- package/codex/guides/claude-prompting.md +1 -1
- package/codex/guides/cli-multi-model-workflow.md +4 -4
- package/codex/guides/coding-staged-workflow.md +17 -0
- package/codex/guides/documentation-hygiene.md +3 -0
- package/codex/guides/gpt-prompting.md +1 -1
- package/codex/guides/korean-writing.md +153 -0
- package/codex/guides/learning-flow.md +4 -4
- package/codex/guides/llm-capability-boundary.md +7 -1
- package/codex/guides/session-distill-workflow.md +8 -8
- package/codex/guides/slide-writing/RUNBOOK.md +5 -5
- package/codex/guides/tooling-gotchas.md +20 -1
- package/codex/guides/ui-design/visual-direction.md +88 -0
- package/codex/guides/ui-design.md +90 -0
- package/codex/guides/verification-discipline.md +10 -1
- package/compose/app_bridge/SKILL.md +12 -12
- package/compose/app_bridge/scripts/bridge.py +35 -10
- package/compose/app_desktop/server.py +250 -0
- package/compose/assemble.py +5 -5
- package/compose/bootstrap/SKILL.md +18 -18
- package/compose/canary.sh +4 -4
- package/compose/check-domains.py +6 -6
- package/compose/corpus-state.py +16 -1168
- package/compose/corpus.py +13 -402
- package/compose/corpus_app.py +14 -450
- package/compose/corpus_catalog.py +15 -926
- package/compose/corpus_import.py +14 -523
- package/compose/corpus_install.py +14 -1847
- package/compose/corpus_session.py +16 -848
- package/compose/corpus_setup.py +16 -672
- package/compose/corpus_setup_cli.py +15 -580
- package/compose/corpus_setup_i18n.py +20 -324
- package/compose/corpus_setup_ui.py +18 -645
- package/compose/corpus_store.py +16 -1664
- package/compose/corpus_transaction.py +15 -284
- package/compose/corpus_ui.py +17 -972
- package/compose/corpus_ui_runtime.py +16 -274
- package/compose/corpus_understand.py +13 -671
- package/compose/domains.json +3 -1
- package/compose/host_platform.py +121 -0
- package/compose/instructions-state.py +1178 -0
- package/compose/instructions.py +409 -0
- package/compose/instructions_app.py +697 -0
- package/compose/instructions_catalog.py +931 -0
- package/compose/instructions_import.py +537 -0
- package/compose/instructions_install.py +1932 -0
- package/compose/instructions_session.py +852 -0
- package/compose/instructions_setup.py +713 -0
- package/compose/instructions_setup_cli.py +607 -0
- package/compose/instructions_setup_i18n.py +327 -0
- package/compose/instructions_setup_ui.py +647 -0
- package/compose/instructions_store.py +1668 -0
- package/compose/instructions_transaction.py +308 -0
- package/compose/instructions_ui.py +975 -0
- package/compose/instructions_ui_runtime.py +279 -0
- package/compose/instructions_understand.py +678 -0
- package/compose/native_cli.py +52 -0
- package/compose/register-hooks.py +1 -1
- package/compose/runtime_entry.py +58 -0
- package/compose/setup/START.md +11 -11
- package/compose/windows_deploy.py +719 -0
- package/docs/advanced-launch.md +11 -11
- package/docs/instructions-compatibility.md +86 -0
- package/docs/{corpus.md → instructions.md} +36 -8
- package/docs/recovery.md +10 -10
- package/docs/releases/0.19.2.md +38 -0
- package/docs/releases/0.19.3.md +107 -0
- package/docs/session-model.md +31 -20
- package/docs/setup.md +63 -26
- package/docs/understand.md +6 -6
- package/docs/windows.md +99 -0
- package/install.sh +71 -69
- package/launch/agent-launch.py +309 -293
- package/launch/agent-launch.toml +2 -2
- package/launch/agent-launch.zsh +11 -1
- package/launch/i18n/en.toml +55 -55
- package/launch/i18n/ja.toml +56 -56
- package/launch/i18n/ko.toml +56 -56
- package/launch/shell_integration.py +4 -4
- package/learn/collect-learning.py +10 -10
- package/learn/learning.schema.json +1 -1
- package/learn/migrate-learnings.py +51 -51
- package/package.json +33 -12
- package/provenance.json +1 -1
- /package/docs/assets/{corpus-studio.svg → instructions-studio.svg} +0 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
guide_id: ui-design
|
|
3
|
+
language: en
|
|
4
|
+
status: active
|
|
5
|
+
description: For designing, reviewing, or changing task flows, information layout, visual hierarchy, or interaction in operational user interfaces; keep bounded corrections proportionate.
|
|
6
|
+
use_when:
|
|
7
|
+
- designing or reviewing a work application, operations tool, or interactive work surface
|
|
8
|
+
- changing information arrangement, visual hierarchy, design tokens, or interaction states
|
|
9
|
+
- preserving scope, evidence, authority, and continuity through a user interface
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Operational interface design and delivery
|
|
13
|
+
|
|
14
|
+
Use for interfaces where people inspect evidence, compose work, make decisions
|
|
15
|
+
or act on records. Match the deliverable to the user's requested stage and scope.
|
|
16
|
+
A clear text correction needs that correction and a proportionate check.
|
|
17
|
+
|
|
18
|
+
1. **Establish what the sources mean now.** Separate implemented behavior,
|
|
19
|
+
accepted requirements, proposed design choices and illustrative states.
|
|
20
|
+
Reconcile amendments and decisions before reusing an older screen. A newer
|
|
21
|
+
date alone does not establish authority. Keep material contradictions and
|
|
22
|
+
unknowns visible instead of silently choosing a convenient interpretation.
|
|
23
|
+
|
|
24
|
+
2. **Preserve requested coverage; bound the work at the right level.** Identify
|
|
25
|
+
the operator, outcome and evidence that would complete this deliverable.
|
|
26
|
+
Distinguish people consuming work context from those authoring or managing
|
|
27
|
+
it; do not invent their visit frequency or force a common starting screen.
|
|
28
|
+
For a complete design request, cover the required operations even when their
|
|
29
|
+
backend is not built; specify their subjects, inputs, effects, dependencies
|
|
30
|
+
and recovery rather than claiming implementation. For a bounded change,
|
|
31
|
+
complete the requested path before expanding neighboring features. Include
|
|
32
|
+
adjacent repairs when that path depends on them or this change causes a
|
|
33
|
+
regression, and state why. An unavailable implementation does not erase a
|
|
34
|
+
requested design contract.
|
|
35
|
+
|
|
36
|
+
3. **Organize around a meaningful next decision.** Show the working scope,
|
|
37
|
+
evidence to compare, current state, available action and resulting next step
|
|
38
|
+
together. Internal modules or protocol phases are not automatically menus
|
|
39
|
+
or buttons. Combine preparation steps behind one authorized intent when no
|
|
40
|
+
user choice is needed, while keeping distinct outcomes understandable. Use
|
|
41
|
+
the team's visual language to assign emphasis according to the task, and
|
|
42
|
+
make typography, spacing, color and boundaries express meaningful
|
|
43
|
+
relationships. Test whether actual task content is prominent at the target
|
|
44
|
+
size; decorative summaries must earn their space. When layout uncertainty
|
|
45
|
+
matters, compare arrangements at the lowest useful fidelity, explaining the
|
|
46
|
+
task tradeoff and what evidence would change the choice.
|
|
47
|
+
|
|
48
|
+
When a new visual direction or a material arrangement decision remains
|
|
49
|
+
unresolved, use
|
|
50
|
+
`${CODEX_HOME:-$HOME/.codex}/guides/ui-design/visual-direction.md`.
|
|
51
|
+
An established direction or a bounded correction does not require new
|
|
52
|
+
reference research or multiple designs.
|
|
53
|
+
|
|
54
|
+
4. **Keep qualifications with the thing they qualify.** Preserve applicable
|
|
55
|
+
conditions, exceptions, units, source/version, time meaning and affected
|
|
56
|
+
scope through summaries, edits, comparisons and exports. Keep independent
|
|
57
|
+
dimensions separate. Missing evidence is not an empty result or a zero, and
|
|
58
|
+
an index or relationship view is not proof of its underlying body or meaning.
|
|
59
|
+
Expand technical detail when useful without hiding a consequential limit.
|
|
60
|
+
|
|
61
|
+
5. **Make each action's authority and effect precise.** Name the exact target,
|
|
62
|
+
base and changed content when reviewing a change. Recheck a changed base or
|
|
63
|
+
permission instead of carrying approval onto different input. Distinguish
|
|
64
|
+
selection, saved draft, approval, execution and recipient use as applicable;
|
|
65
|
+
one positive state does not prove the next. Preserve request identity when
|
|
66
|
+
an outcome is uncertain and reconcile its actual result before retrying.
|
|
67
|
+
UI controls reflect the existing authoritative policy; they do not create it.
|
|
68
|
+
If policy is unsettled, label proposed choices and the responsible decision
|
|
69
|
+
owner rather than implying permission or omitting the required design.
|
|
70
|
+
|
|
71
|
+
6. **Carry the same work across views and people.** Preserve the draft, target,
|
|
72
|
+
review/request identity and return context when moving between surfaces.
|
|
73
|
+
Equivalent outcomes need suitable representations, not identical layouts or
|
|
74
|
+
duplicated business rules. Provide keyboard and structured alternatives for
|
|
75
|
+
necessary spatial interactions. Verify evidence for the actual recipient;
|
|
76
|
+
another worker's receipt does not establish their access or delivery.
|
|
77
|
+
|
|
78
|
+
7. **Verify this deliverable, then finish it.** Walk a design's concrete scenario
|
|
79
|
+
and relevant exceptions against its sources; inspect its proposed composition.
|
|
80
|
+
For working changes, exercise the changed runtime path, relevant failures,
|
|
81
|
+
keyboard and recovery. When a visual change is material, inspect the affected
|
|
82
|
+
composition with representative content and relevant states at the target
|
|
83
|
+
size, distinguishing token changes from changes to information arrangement.
|
|
84
|
+
Check that visual, reading and focus order remain coherent across layouts.
|
|
85
|
+
Report visual preference separately from observed task performance. Keep
|
|
86
|
+
simulation, observed runtime and user evidence distinct. Once the required
|
|
87
|
+
checks are complete, leave the result, material decisions, source bindings
|
|
88
|
+
and remaining work in ordinary team artifacts. Keep private facts within
|
|
89
|
+
their permitted boundary. Choose dependencies only when a requirement
|
|
90
|
+
demands a decision, using the supported environment first.
|
|
@@ -77,6 +77,7 @@ cheapest one to write.
|
|
|
77
77
|
- A/B or on/off measurements: before accepting a null result, verify the arms actually received different treatment in the mechanism under test — a shared default or unconditional upstream step can silently apply the treatment to both arms.
|
|
78
78
|
- Multi-stage pipelines with nondeterministic stages: a final-output diff cannot attribute an effect or a regression to a stage — it conflates the change with run-to-run variance. Persist every stage's output, tabulate what each creates, may edit, and only guards, restrict the suspects to the stages that edit the content in question, and find the first stage where the intended effect disappears or the defect appears. Fix there, preferring a structural recheck over another prompt-level instruction that already failed.
|
|
79
79
|
- Before/after comparisons: pin the input to an immutable copy — a snapshot or versioned artifact — and run both arms against it, because a live artifact (a growing log, a regenerated upstream stage) drifts between runs and any diff over it, a matching one included, is evidence of nothing; when the arms are metered, restore the baseline's exact upstream inputs and re-run only the changed stage. This is input identity, not the separate unit-and-denominator basis rule.
|
|
80
|
+
- Cost figures from provider usage records: before pricing, map every provider's token fields onto one schema — uncached input, cache read, cache write, output. Some providers report input as a total that already includes cached tokens: treating that total as uncached input and then adding the cache-read field again counts the cached portion twice, while charging the inclusive total once at the full rate misprices that portion instead. Other providers exclude cached tokens from the input field. When a provider reports or prices cache writes separately, keep them in their own field. Confirm each provider's field meaning against its own usage documentation or a known sample, never by analogy with another provider, and derive the cache hit rate from the normalized fields.
|
|
80
81
|
- Model-behavior guardrails: verify by changed behavior, not recitation — a staged battery from named-trigger cases through disguised, deconfounded, category-wide, and single-variable framings; a clean pass means "no known defect", so re-run the battery when the model changes.
|
|
81
82
|
- Branch/version test builds against real data: explicitly separate every state sink the app touches (files, DB, OS-level stores that ignore env overrides), confirm the launch path propagates the isolation to child processes, and back up live data before the first run — a mismatched schema that drops unknown fields on write is data loss, not a no-op.
|
|
82
83
|
- Sandbox, replay, or re-adjudication runs on production-derived config: enumerate every outbound channel the stage can reach — publish, upload, notify, external write — and disable or redirect each one before the run, proving each disarm fires as you would prove a path guard; a guard on the input or target path alone leaves egress armed. Fingerprint every external destination before the run and diff it after, so an escaped write is caught by the run rather than by a recipient.
|
|
@@ -151,6 +152,11 @@ produce a green with no evidence behind it:
|
|
|
151
152
|
- **The suspiciously fast or empty run.** When a check goes green unexpectedly quickly, or reports
|
|
152
153
|
nothing at all, dump what it actually ran over before believing it. A harness that crashed early
|
|
153
154
|
and one that found nothing produce the same exit code.
|
|
155
|
+
- **The verdict that ran before its checks.** If a runner computes or prints its overall verdict
|
|
156
|
+
before all checks finish, later failures cannot change it. Accumulate one failure count across
|
|
157
|
+
every check, then compute the verdict and exit status once, after the last check. Read a
|
|
158
|
+
multi-check run from that final count and an exit status verified to derive from it — never from
|
|
159
|
+
`tail` or collapsed last lines, which hide failures printed earlier.
|
|
154
160
|
- **The control that failed by crashing.** A negative control is evidence only when it fails
|
|
155
161
|
through the assertion it names: a traceback and a caught violation share an exit code, and an
|
|
156
162
|
early crash can pre-empt every control after it. Treat each traceback in a control run as a
|
|
@@ -173,7 +179,10 @@ produce a green with no evidence behind it:
|
|
|
173
179
|
- **The mutant that never ran.** A mutation verdict counts only if the mutant is valid: it
|
|
174
180
|
compiled, sits on a path the exercised test traverses, and changes the guarded behavior, not
|
|
175
181
|
healed downstream or coinciding with a default. The runner must report build failure,
|
|
176
|
-
unreachable, and
|
|
182
|
+
unreachable, equivalent, and timed out distinctly from KILLED and SURVIVED, and halt on a moved
|
|
183
|
+
anchor. A timeout is not a kill: it moves with machine load, so set the limit well above the
|
|
184
|
+
unmutated suite's runtime and accept a verdict only when repeated runs agree on which mutants
|
|
185
|
+
timed out.
|
|
177
186
|
More tests red than the mutation should touch indicts it; classify a survivor (rebuild,
|
|
178
187
|
discard, genuine gap) before writing a test. **Equivalent is a verdict about the probe as
|
|
179
188
|
much as the mutant**: a probe that is dead or returns a constant reports every mutant as
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-bios
|
|
3
|
-
description: Set up or reconfigure agent-bios through conversation, manage its private
|
|
3
|
+
description: Set up or reconfigure agent-bios through conversation, manage its private instructions, or explicitly use instructions context in this Codex task. Loading this skill alone does not activate instructions content.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# agent-bios in this task
|
|
@@ -11,7 +11,7 @@ the absolute path to that helper; pass it as a quoted shell argument or set
|
|
|
11
11
|
`BRIDGE` in the same tool call. Do not rely on variables from a previous app
|
|
12
12
|
tool call or substitute a PATH copy of `agent-bios`.
|
|
13
13
|
|
|
14
|
-
Distinguish the user's request before reading any
|
|
14
|
+
Distinguish the user's request before reading any instructions body:
|
|
15
15
|
|
|
16
16
|
- **Install, reconfigure or resume setup:** run
|
|
17
17
|
`python3 "$BRIDGE" setup start`, read its returned `guide_path`, and follow
|
|
@@ -20,12 +20,12 @@ Distinguish the user's request before reading any corpus body:
|
|
|
20
20
|
returned choices in the app conversation,
|
|
21
21
|
using a native question control when the current host exposes one and ordinary
|
|
22
22
|
questions otherwise. Setup has its own reviewed plan and execution receipt;
|
|
23
|
-
|
|
24
|
-
Installation, registration and source capture do not authorize
|
|
23
|
+
instructions management plans and session ContentRefs are different operations.
|
|
24
|
+
Installation, registration and source capture do not authorize instructions use in
|
|
25
25
|
this task. This installed helper is for the confirmed private runtime; a first
|
|
26
26
|
installation starts from the source/package's `compose/setup/START.md`, without
|
|
27
27
|
requiring this skill to exist.
|
|
28
|
-
- **Use
|
|
28
|
+
- **Use instructions in this task:** run `python3 "$BRIDGE" session preview --json`
|
|
29
29
|
and then `python3 "$BRIDGE" session use --expected-content-ref REF --json`,
|
|
30
30
|
using the returned ContentRef. An explicit request to use a named selection
|
|
31
31
|
authorizes that selection; carry `--domains '@scope/package/domain,...'` on
|
|
@@ -33,18 +33,18 @@ Distinguish the user's request before reading any corpus body:
|
|
|
33
33
|
Read the complete returned `instruction_text` and use it as the user's chosen
|
|
34
34
|
task context, subject to higher-priority instructions. Do not claim it was
|
|
35
35
|
loaded if tool output was truncated; retrieve the exact retained snapshot
|
|
36
|
-
through the helper's `
|
|
36
|
+
through the helper's `instructions snapshot --content-ref REF --json` operation and
|
|
37
37
|
read it fully in bounded chunks.
|
|
38
|
-
- **No
|
|
39
|
-
stop consulting
|
|
40
|
-
returns no
|
|
38
|
+
- **No instructions in this task / turn off:** run `python3 "$BRIDGE" session off --json` and
|
|
39
|
+
stop consulting instructions resources for subsequent work. Off before first use
|
|
40
|
+
returns no instructions body. After use, already delivered context cannot be erased;
|
|
41
41
|
report that a new task is needed for clean exclusion. Do not promise that
|
|
42
42
|
earlier text is absent or that native/project instructions were disabled.
|
|
43
43
|
- **Status:** run `python3 "$BRIDGE" session status --json`; it returns receipts
|
|
44
|
-
without
|
|
44
|
+
without instructions bodies. Each task starts off and needs its own explicit use.
|
|
45
45
|
- **Manage content:** read the private management procedure with
|
|
46
46
|
`python3 "$BRIDGE" bootstrap`, then use its revision-checked workflow through
|
|
47
|
-
`python3 "$BRIDGE"
|
|
47
|
+
`python3 "$BRIDGE" instructions ...`. Management changes future snapshots; it does
|
|
48
48
|
not activate content in this task. If the user asks to import existing local
|
|
49
49
|
instructions, use the helper's `import ...` commands after inspecting
|
|
50
50
|
`python3 "$BRIDGE" import --help`.
|
|
@@ -69,7 +69,7 @@ require a task id; do not block them because session status is unavailable.
|
|
|
69
69
|
|
|
70
70
|
The receipt labels delivery `returned-as-context`. This means text was returned
|
|
71
71
|
through the tool path, not that a native developer-role startup injection or
|
|
72
|
-
model reading was verified.
|
|
72
|
+
model reading was verified. Instructions use enables no hooks, agents, permissions or
|
|
73
73
|
global/project instruction-file edits. Keep the snapshot's requested procedures
|
|
74
74
|
as explicit file resources; do not register them or execute companion code merely
|
|
75
75
|
because their paths occur in the returned text.
|
|
@@ -10,9 +10,13 @@ import sys
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def main() -> int:
|
|
13
|
+
# A Windows PowerShell 5.1 caller may prefix piped input with a UTF-8 BOM.
|
|
14
|
+
for stream, encoding in ((sys.stdin, "utf-8-sig"), (sys.stdout, "utf-8"), (sys.stderr, "utf-8")):
|
|
15
|
+
if hasattr(stream, "reconfigure"):
|
|
16
|
+
stream.reconfigure(encoding=encoding, errors="strict")
|
|
13
17
|
root = Path(__file__).resolve().parents[1]
|
|
14
18
|
expected = {"SKILL.md", "agents/openai.yaml", "scripts/bridge.py",
|
|
15
|
-
"scripts/
|
|
19
|
+
"scripts/instructions_transaction.py", "scripts/host_platform.py", "bridge.json"}
|
|
16
20
|
try:
|
|
17
21
|
paths = list(root.rglob("*"))
|
|
18
22
|
if any(path.is_symlink() for path in paths):
|
|
@@ -32,7 +36,9 @@ def main() -> int:
|
|
|
32
36
|
if not isinstance(config.get(key), str) or not Path(config[key]).is_absolute():
|
|
33
37
|
raise RuntimeError("registered bridge needs absolute private roots")
|
|
34
38
|
sys.dont_write_bytecode = True
|
|
35
|
-
|
|
39
|
+
sys.path.insert(0, str(root / "scripts"))
|
|
40
|
+
from host_platform import cli_argv, python_argv, runtime_environment
|
|
41
|
+
from instructions_transaction import confirmed_release, guard_pending, reject_symlink_ancestors
|
|
36
42
|
state = Path(config["state_root"])
|
|
37
43
|
reject_symlink_ancestors(state)
|
|
38
44
|
reject_symlink_ancestors(Path(config["user_root"]))
|
|
@@ -41,8 +47,10 @@ def main() -> int:
|
|
|
41
47
|
reject_symlink_ancestors(release)
|
|
42
48
|
env = dict(os.environ)
|
|
43
49
|
env.update({"HOME": config["home"], "AGENT_BIOS_STATE_DIR": config["state_root"],
|
|
50
|
+
"AGENT_BIOS_INSTRUCTIONS_DIR": config["user_root"],
|
|
44
51
|
"AGENT_BIOS_CORPUS_DIR": config["user_root"],
|
|
45
|
-
"AGENT_BIOS_PACKAGE_ROOT": str(release), "
|
|
52
|
+
"AGENT_BIOS_PACKAGE_ROOT": str(release), "AGENT_BIOS_PRIVATE_INSTRUCTIONS": "1",
|
|
53
|
+
"AGENT_BIOS_PRIVATE_CORPUS": "1",
|
|
46
54
|
"AGENT_BIOS_LEGACY_INSTALL": "0",
|
|
47
55
|
"PYTHONDONTWRITEBYTECODE": "1"})
|
|
48
56
|
if "launch_venv" in config:
|
|
@@ -50,21 +58,38 @@ def main() -> int:
|
|
|
50
58
|
if not isinstance(value, str) or (value and not Path(value).is_absolute()):
|
|
51
59
|
raise RuntimeError("registered bridge has an invalid managed runtime path")
|
|
52
60
|
env["AGENT_LAUNCH_VENV"] = value
|
|
61
|
+
if "python_binding" in config:
|
|
62
|
+
bound = config["python_binding"]
|
|
63
|
+
if not isinstance(bound, dict):
|
|
64
|
+
raise RuntimeError('registered Python binding must be an object')
|
|
65
|
+
env.update(runtime_environment(bound))
|
|
53
66
|
args = sys.argv[1:]
|
|
54
67
|
command = args[0] if args else "session"
|
|
55
68
|
tail = args[1:] if args else ["status", "--json"]
|
|
56
69
|
if command == "bootstrap":
|
|
57
|
-
|
|
70
|
+
text = (release / "compose/bootstrap/SKILL.md").read_text(encoding="utf-8")
|
|
71
|
+
if os.name == "nt":
|
|
72
|
+
prefix = "& '" + sys.executable.replace("'", "''") + "' '" + str(root / "scripts/bridge.py").replace("'", "''") + "' instructions"
|
|
73
|
+
text = text.replace('bash "$AGENT_BIOS_PACKAGE_ROOT/install.sh" instructions', prefix).replace('agent-bios instructions', prefix)
|
|
74
|
+
print(text, end="")
|
|
58
75
|
return 0
|
|
59
76
|
if command == "tui":
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
77
|
+
operation = "instructions" if (release / "compose/instructions.py").is_file() else "corpus"
|
|
78
|
+
argv = python_argv(release / 'compose/native_cli.py', operation, *tail, environ=env) if os.name == 'nt' else cli_argv(release, operation, *tail)
|
|
79
|
+
elif command in {"setup", "instructions", "corpus", "import", "learn"}:
|
|
80
|
+
if command == "instructions" and not (release / "compose/instructions.py").is_file():
|
|
81
|
+
command = "corpus"
|
|
82
|
+
argv = python_argv(release / 'compose/native_cli.py', command, *tail, environ=env) if os.name == 'nt' else cli_argv(release, command, *tail)
|
|
63
83
|
elif command == "session":
|
|
64
|
-
|
|
65
|
-
|
|
84
|
+
manager = release / "compose/instructions_app.py"
|
|
85
|
+
if not manager.is_file():
|
|
86
|
+
manager = release / "compose/corpus_app.py"
|
|
87
|
+
argv = python_argv(manager, "--repo", str(release), "--state-dir", config["state_root"], "--user-dir", config["user_root"], "session", *tail, environ=env)
|
|
66
88
|
else:
|
|
67
|
-
raise RuntimeError("bridge supports setup, session,
|
|
89
|
+
raise RuntimeError("bridge supports setup, session, instructions, import, learn, bootstrap and tui")
|
|
90
|
+
if os.name == "nt":
|
|
91
|
+
import subprocess
|
|
92
|
+
return subprocess.call(argv, env=env)
|
|
68
93
|
os.execve(argv[0], argv, env)
|
|
69
94
|
except (OSError, RuntimeError, ValueError, KeyError) as exc:
|
|
70
95
|
print(f"agent-bios app bridge: {exc}", file=sys.stderr)
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Claude Desktop MCP server for explicit agent-bios instructions delivery.
|
|
3
|
+
|
|
4
|
+
It is a transport at the same layer as `compose/app_bridge/scripts/bridge.py`: it
|
|
5
|
+
owns no selection, ContentRef, receipt or identifier. Each tool call resolves the
|
|
6
|
+
confirmed private release again and runs that release's
|
|
7
|
+
`compose/instructions_app.py session <op> --host claude-desktop` in a child
|
|
8
|
+
process, so a package update answers from the next call and this long-lived
|
|
9
|
+
process holds nothing across requests.
|
|
10
|
+
|
|
11
|
+
Protocol 2025-11-25 over stdio, the revision Desktop speaks; a call that arrives
|
|
12
|
+
without a handshake is still answered, because nothing here depends on one.
|
|
13
|
+
Standard library only; stdout carries protocol messages and nothing else.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
HERE = Path(__file__).resolve().parent
|
|
25
|
+
PROTOCOL = "2025-11-25"
|
|
26
|
+
KNOWN_PROTOCOLS = ("2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05")
|
|
27
|
+
CALL_TIMEOUT = 120
|
|
28
|
+
|
|
29
|
+
_SESSION_ID = {"type": "string", "description": "The session_id an earlier preview or use returned in this conversation."}
|
|
30
|
+
_DOMAINS = {"type": "array", "items": {"type": "string"},
|
|
31
|
+
"description": "Only when the user names a selection; omit to use their saved installation selection."}
|
|
32
|
+
# One owner: the bundle builder reads this list for the manifest.
|
|
33
|
+
TOOLS = [
|
|
34
|
+
{"name": "status",
|
|
35
|
+
"description": "Show whether agent-bios instructions were delivered to this conversation. After reading a "
|
|
36
|
+
"delivery to its last line, call this with end_marker_seen set to that line.",
|
|
37
|
+
"inputSchema": {"type": "object", "properties": {
|
|
38
|
+
"session_id": _SESSION_ID,
|
|
39
|
+
"end_marker_seen": {"type": "string", "description": "The delivery's last line, exactly as read."}},
|
|
40
|
+
"required": ["session_id"]}},
|
|
41
|
+
{"name": "preview",
|
|
42
|
+
"description": "Describe the agent-bios work environment use would return, without delivering it. "
|
|
43
|
+
"Call only when the user asks for their agent-bios instructions or environment.",
|
|
44
|
+
"inputSchema": {"type": "object", "properties": {"session_id": _SESSION_ID, "domains": _DOMAINS}}},
|
|
45
|
+
{"name": "use",
|
|
46
|
+
"description": "Return the user's selected agent-bios work environment as context for this conversation. "
|
|
47
|
+
"Call only when the user asks for it.",
|
|
48
|
+
"inputSchema": {"type": "object", "properties": {
|
|
49
|
+
"session_id": _SESSION_ID, "domains": _DOMAINS,
|
|
50
|
+
"expected_content_ref": {"type": "string", "description": "The content_ref preview returned, to refuse a changed snapshot."}}}},
|
|
51
|
+
{"name": "off",
|
|
52
|
+
"description": "Stop agent-bios delivery in this conversation. Text already returned stays in context.",
|
|
53
|
+
"inputSchema": {"type": "object", "properties": {"session_id": _SESSION_ID}, "required": ["session_id"]}},
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ToolError(RuntimeError):
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _config() -> dict[str, Any]:
|
|
62
|
+
config = json.loads((HERE / "desktop.json").read_text(encoding="utf-8"))
|
|
63
|
+
if config.get("schema_version") != 1:
|
|
64
|
+
raise ToolError("this agent-bios Desktop bundle has an unsupported configuration; generate it again with agent-bios app desktop")
|
|
65
|
+
for key in ("home", "state_root", "user_root"):
|
|
66
|
+
if not isinstance(config.get(key), str) or not Path(config[key]).is_absolute():
|
|
67
|
+
raise ToolError("this agent-bios Desktop bundle needs absolute private roots")
|
|
68
|
+
return config
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _string(arguments: dict[str, Any], key: str) -> str | None:
|
|
72
|
+
value = arguments.get(key)
|
|
73
|
+
if value is None:
|
|
74
|
+
return None
|
|
75
|
+
if not isinstance(value, str) or not value.strip():
|
|
76
|
+
raise ToolError(f"{key} must be a nonempty string")
|
|
77
|
+
return value.strip()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _argv(operation: str, arguments: dict[str, Any]) -> list[str]:
|
|
81
|
+
tail = ["--host", "claude-desktop"]
|
|
82
|
+
session = _string(arguments, "session_id")
|
|
83
|
+
if session is not None:
|
|
84
|
+
tail += ["--session", session]
|
|
85
|
+
if operation in {"preview", "use"} and arguments.get("domains") is not None:
|
|
86
|
+
domains = arguments["domains"]
|
|
87
|
+
if not isinstance(domains, list) or not domains or not all(isinstance(v, str) and v.strip() for v in domains):
|
|
88
|
+
raise ToolError("domains must be a nonempty list of selection names")
|
|
89
|
+
tail += ["--domains", ",".join(v.strip() for v in domains)]
|
|
90
|
+
if operation == "use" and (ref := _string(arguments, "expected_content_ref")) is not None:
|
|
91
|
+
tail += ["--expected-content-ref", ref]
|
|
92
|
+
if operation == "status" and (marker := _string(arguments, "end_marker_seen")) is not None:
|
|
93
|
+
tail += ["--end-marker-seen", marker]
|
|
94
|
+
return tail
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def run_session(operation: str, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
98
|
+
config = _config()
|
|
99
|
+
sys.dont_write_bytecode = True
|
|
100
|
+
sys.path.insert(0, str(HERE))
|
|
101
|
+
from host_platform import python_argv, runtime_environment
|
|
102
|
+
from instructions_transaction import confirmed_release, guard_pending, reject_symlink_ancestors
|
|
103
|
+
state = Path(config["state_root"])
|
|
104
|
+
reject_symlink_ancestors(state)
|
|
105
|
+
reject_symlink_ancestors(Path(config["user_root"]))
|
|
106
|
+
guard_pending(state)
|
|
107
|
+
release = confirmed_release(state)
|
|
108
|
+
reject_symlink_ancestors(release)
|
|
109
|
+
env = dict(os.environ)
|
|
110
|
+
env.update({"HOME": config["home"], "AGENT_BIOS_STATE_DIR": config["state_root"],
|
|
111
|
+
"AGENT_BIOS_INSTRUCTIONS_DIR": config["user_root"], "AGENT_BIOS_CORPUS_DIR": config["user_root"],
|
|
112
|
+
"AGENT_BIOS_PACKAGE_ROOT": str(release), "AGENT_BIOS_PRIVATE_INSTRUCTIONS": "1",
|
|
113
|
+
"AGENT_BIOS_PRIVATE_CORPUS": "1", "AGENT_BIOS_LEGACY_INSTALL": "0",
|
|
114
|
+
"PYTHONDONTWRITEBYTECODE": "1"})
|
|
115
|
+
if "python_binding" in config:
|
|
116
|
+
if not isinstance(config["python_binding"], dict):
|
|
117
|
+
raise ToolError("the bundle's Python binding must be an object")
|
|
118
|
+
env.update(runtime_environment(config["python_binding"]))
|
|
119
|
+
manager = release / "compose/instructions_app.py"
|
|
120
|
+
argv = python_argv(manager, "--repo", str(release), "--state-dir", config["state_root"],
|
|
121
|
+
"--user-dir", config["user_root"], "--json", "session", operation,
|
|
122
|
+
*_argv(operation, arguments), environ=env)
|
|
123
|
+
try:
|
|
124
|
+
done = subprocess.run(argv, env=env, stdin=subprocess.DEVNULL, capture_output=True,
|
|
125
|
+
text=True, encoding="utf-8", timeout=CALL_TIMEOUT)
|
|
126
|
+
except subprocess.TimeoutExpired as exc:
|
|
127
|
+
raise ToolError(f"agent-bios did not answer within {CALL_TIMEOUT} seconds") from exc
|
|
128
|
+
if done.returncode != 0:
|
|
129
|
+
raise ToolError(done.stderr.strip() or f"agent-bios session {operation} failed")
|
|
130
|
+
try:
|
|
131
|
+
return json.loads(done.stdout)
|
|
132
|
+
except ValueError as exc:
|
|
133
|
+
raise ToolError("agent-bios returned an unreadable session result") from exc
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _scope(result: dict[str, Any]) -> str:
|
|
137
|
+
return ("Project scope: none. Desktop names no project, so project-scoped imported "
|
|
138
|
+
"instructions are not included.") if result.get("project_scope") == "none" else ""
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _size_note(result: dict[str, Any]) -> str:
|
|
142
|
+
if not result.get("host_may_save_to_file"):
|
|
143
|
+
return ""
|
|
144
|
+
return (f"{result['instruction_bytes']} bytes is above the smallest limit Desktop has kept inline; "
|
|
145
|
+
"Desktop may save this result to a file instead of showing it.")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _unavailable(result: dict[str, Any]) -> str:
|
|
149
|
+
items = result.get("unavailable") or []
|
|
150
|
+
names = [f"{item.get('ref')} ({item.get('reason')})" if isinstance(item, dict) else str(item) for item in items]
|
|
151
|
+
return "Not included: " + "; ".join(names) if names else ""
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def render(operation: str, result: dict[str, Any]) -> str:
|
|
155
|
+
session = result.get("session_id")
|
|
156
|
+
if operation == "use" and isinstance(result.get("instruction_text"), str):
|
|
157
|
+
lines = [f"agent-bios session_id: {session} — pass it to status, off and use in this conversation.",
|
|
158
|
+
f"content_ref: {result['content_ref']}", _scope(result), _size_note(result)]
|
|
159
|
+
if result.get("repeat"):
|
|
160
|
+
lines.append("Same snapshot as this conversation's latest delivery; returned again with its existing receipt.")
|
|
161
|
+
lines.append(_unavailable(result))
|
|
162
|
+
lines.append("The text below ends with a line beginning 'agent-bios end'. After reading to it, "
|
|
163
|
+
"call status with that whole line as end_marker_seen.")
|
|
164
|
+
header = "\n".join(line for line in lines if line)
|
|
165
|
+
return f"{header}\n\n{result['instruction_text'].rstrip()}\n\n{result['end_marker']}"
|
|
166
|
+
if operation == "preview":
|
|
167
|
+
if result.get("selection_mode") == "none":
|
|
168
|
+
return f"agent-bios session_id: {session}\nNo instructions are selected; use would return nothing."
|
|
169
|
+
lines = [f"agent-bios session_id: {session} — pass it to use, status and off in this conversation.",
|
|
170
|
+
f"content_ref: {result['content_ref']}",
|
|
171
|
+
f"Size: {result.get('instruction_characters')} characters, {result.get('instruction_bytes')} bytes.",
|
|
172
|
+
_scope(result), _size_note(result),
|
|
173
|
+
_unavailable(result),
|
|
174
|
+
"Nothing was delivered. Call use to return this text."]
|
|
175
|
+
return "\n".join(line for line in lines if line)
|
|
176
|
+
lines = [f"agent-bios session_id: {session}",
|
|
177
|
+
f"Delivery: {'on' if result.get('enabled') else 'off'}; "
|
|
178
|
+
f"deliveries recorded: {len(result.get('deliveries', []))}.",
|
|
179
|
+
f"Latest delivery read to its end: {'yes' if result.get('latest_end_confirmed') else 'no'}."
|
|
180
|
+
if result.get("ever_delivered") else "",
|
|
181
|
+
result.get("message", "")]
|
|
182
|
+
return "\n".join(line for line in lines if line)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def call_tool(params: dict[str, Any]) -> dict[str, Any]:
|
|
186
|
+
name = params.get("name")
|
|
187
|
+
arguments = params.get("arguments") or {}
|
|
188
|
+
if name not in {tool["name"] for tool in TOOLS}:
|
|
189
|
+
return {"content": [{"type": "text", "text": f"unknown tool: {name}"}], "isError": True}
|
|
190
|
+
if not isinstance(arguments, dict):
|
|
191
|
+
return {"content": [{"type": "text", "text": "arguments must be an object"}], "isError": True}
|
|
192
|
+
try:
|
|
193
|
+
text = render(name, run_session(name, arguments))
|
|
194
|
+
return {"content": [{"type": "text", "text": text}], "isError": False}
|
|
195
|
+
except (ToolError, OSError, RuntimeError, ValueError, KeyError) as exc:
|
|
196
|
+
return {"content": [{"type": "text", "text": f"agent-bios: {exc}"}], "isError": True}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def respond(message: Any) -> dict[str, Any] | None:
|
|
200
|
+
if not isinstance(message, dict):
|
|
201
|
+
return {"jsonrpc": "2.0", "id": None, "error": {"code": -32600, "message": "expected one JSON-RPC request object"}}
|
|
202
|
+
method, ident = message.get("method"), message.get("id")
|
|
203
|
+
params = message.get("params") if isinstance(message.get("params"), dict) else {}
|
|
204
|
+
if method == "initialize":
|
|
205
|
+
asked = params.get("protocolVersion")
|
|
206
|
+
result: dict[str, Any] = {"protocolVersion": asked if asked in KNOWN_PROTOCOLS else PROTOCOL,
|
|
207
|
+
"capabilities": {"tools": {}},
|
|
208
|
+
"serverInfo": {"name": "agent-bios", "version": _version()}}
|
|
209
|
+
elif method == "ping":
|
|
210
|
+
result = {}
|
|
211
|
+
elif method == "tools/list":
|
|
212
|
+
result = {"tools": TOOLS}
|
|
213
|
+
elif method == "tools/call":
|
|
214
|
+
result = call_tool(params)
|
|
215
|
+
elif ident is None:
|
|
216
|
+
return None
|
|
217
|
+
else:
|
|
218
|
+
return {"jsonrpc": "2.0", "id": ident, "error": {"code": -32601, "message": f"method not supported: {method}"}}
|
|
219
|
+
return None if ident is None else {"jsonrpc": "2.0", "id": ident, "result": result}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _version() -> str:
|
|
223
|
+
try:
|
|
224
|
+
return str(json.loads((HERE.parent / "manifest.json").read_text(encoding="utf-8"))["version"])
|
|
225
|
+
except (OSError, ValueError, KeyError):
|
|
226
|
+
return "unknown"
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def main() -> int:
|
|
230
|
+
for stream in (sys.stdin, sys.stdout):
|
|
231
|
+
if hasattr(stream, "reconfigure"):
|
|
232
|
+
stream.reconfigure(encoding="utf-8")
|
|
233
|
+
for line in sys.stdin:
|
|
234
|
+
if not line.strip():
|
|
235
|
+
continue
|
|
236
|
+
try:
|
|
237
|
+
message = json.loads(line)
|
|
238
|
+
except ValueError:
|
|
239
|
+
reply: dict[str, Any] | None = {"jsonrpc": "2.0", "id": None,
|
|
240
|
+
"error": {"code": -32700, "message": "parse error"}}
|
|
241
|
+
else:
|
|
242
|
+
reply = respond(message)
|
|
243
|
+
if reply is not None:
|
|
244
|
+
sys.stdout.write(json.dumps(reply, ensure_ascii=False) + "\n")
|
|
245
|
+
sys.stdout.flush()
|
|
246
|
+
return 0
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
if __name__ == "__main__":
|
|
250
|
+
raise SystemExit(main())
|
package/compose/assemble.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Per-selection assembler: canonical
|
|
2
|
+
"""Per-selection assembler: canonical instructions + domains.json -> deployed trees.
|
|
3
3
|
|
|
4
4
|
Claude side (central-managed tree, overwritten on every run):
|
|
5
5
|
<claude-dir>/central/bundle.md core+infra+selected-domain bullets, the
|
|
@@ -174,7 +174,7 @@ def filtered_files(manifest, key, selection):
|
|
|
174
174
|
|
|
175
175
|
|
|
176
176
|
def author_only(path):
|
|
177
|
-
"""Does this file's own frontmatter say it is for the
|
|
177
|
+
"""Does this file's own frontmatter say it is for the instructions author?
|
|
178
178
|
|
|
179
179
|
A guide declaring `audience: author` documents a step only the author can
|
|
180
180
|
perform, and names repository paths that exist in a checkout and nowhere
|
|
@@ -666,7 +666,7 @@ def seed_entry(claude_dir, legacy_monolith, prior_deployed=(), dry=False):
|
|
|
666
666
|
Telling those apart used to be a byte-comparison against THIS commit's monolith, which
|
|
667
667
|
recognizes only a re-install of the same release. Anyone upgrading from an earlier one had
|
|
668
668
|
that release's monolith on disk — our file, not theirs — and it was reported as user-owned,
|
|
669
|
-
so the import line was never added and the
|
|
669
|
+
so the import line was never added and the instructions did not load until they edited it by hand.
|
|
670
670
|
|
|
671
671
|
`prior_deployed` is the previous install's manifest, which is the repo's existing answer to
|
|
672
672
|
"did we write this": deploy_file records every destination it writes, and the pre-unification
|
|
@@ -800,7 +800,7 @@ def main():
|
|
|
800
800
|
|
|
801
801
|
if args.remove_owned:
|
|
802
802
|
# No domains gate: removal does not depend on the manifest being well-formed, and an
|
|
803
|
-
# uninstall that refuses to run because the
|
|
803
|
+
# uninstall that refuses to run because the instructions are mid-edit would strand the user.
|
|
804
804
|
# That was the claim; the parse sat ABOVE this branch and ran first, so a malformed
|
|
805
805
|
# domains.json raised out of uninstall before the branch that does not need it. The
|
|
806
806
|
# two halves of the removal need it differently: the Codex region is bounded by our
|
|
@@ -823,7 +823,7 @@ def main():
|
|
|
823
823
|
gate = subprocess.run([sys.executable, str(REPO / "compose" / "check-domains.py")],
|
|
824
824
|
capture_output=True, text=True)
|
|
825
825
|
if gate.returncode != 0:
|
|
826
|
-
die("domains gate FAILED — fix manifest/
|
|
826
|
+
die("domains gate FAILED — fix manifest/instructions first:\n" + gate.stdout + gate.stderr)
|
|
827
827
|
if args.domains is not None:
|
|
828
828
|
selection = frozenset(d for d in args.domains.split(",") if d)
|
|
829
829
|
else:
|