microcoreos 0.1.0__py3-none-any.whl

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 (112) hide show
  1. microcoreos/__init__.py +23 -0
  2. microcoreos/_template/.agent/skills/microcoreos-architecture/SKILL.md +24 -0
  3. microcoreos/_template/.agent/skills/microcoreos-architecture/agent.md +18 -0
  4. microcoreos/_template/.agent/workflows/feature-plan.md +111 -0
  5. microcoreos/_template/.agent/workflows/multi-domain-plan.md +96 -0
  6. microcoreos/_template/.agent/workflows/new-domain.md +201 -0
  7. microcoreos/_template/.agent/workflows/new-tool.md +72 -0
  8. microcoreos/_template/.dockerignore +23 -0
  9. microcoreos/_template/.env.example +96 -0
  10. microcoreos/_template/.gitignore +57 -0
  11. microcoreos/_template/AGENTS.md +175 -0
  12. microcoreos/_template/Dockerfile +35 -0
  13. microcoreos/_template/INSTRUCTIONS_FOR_AI.md +560 -0
  14. microcoreos/_template/dev_infra/cache_probe.py +176 -0
  15. microcoreos/_template/dev_infra/docker-compose.yml +137 -0
  16. microcoreos/_template/docs/CLI.md +187 -0
  17. microcoreos/_template/docs/CORE_INFRASTRUCTURE.md +372 -0
  18. microcoreos/_template/docs/ELASTIC_DEPLOYMENT.md +277 -0
  19. microcoreos/_template/docs/EVENT_BUS.md +288 -0
  20. microcoreos/_template/docs/HTTP_SERVER.md +98 -0
  21. microcoreos/_template/docs/INDEX.md +41 -0
  22. microcoreos/_template/docs/OBSERVABILITY.md +56 -0
  23. microcoreos/_template/docs/OBSERVABILITY_API.md +183 -0
  24. microcoreos/_template/docs/PARALLEL_DEVELOPMENT.md +494 -0
  25. microcoreos/_template/docs/PLAN_EVENT_LINTER.md +95 -0
  26. microcoreos/_template/docs/TECH_DEBT.md +388 -0
  27. microcoreos/_template/docs/translations/es/README.md +265 -0
  28. microcoreos/_template/domains/devtools/lint/plugin_sources.py +51 -0
  29. microcoreos/_template/domains/devtools/plugins/discovery_naming_linter_plugin.py +89 -0
  30. microcoreos/_template/domains/devtools/plugins/domain_isolation_linter_plugin.py +80 -0
  31. microcoreos/_template/domains/devtools/plugins/event_contract_linter_plugin.py +420 -0
  32. microcoreos/_template/domains/devtools/plugins/event_schemas_plugin.py +94 -0
  33. microcoreos/_template/domains/devtools/plugins/field_divergence_linter_plugin.py +178 -0
  34. microcoreos/_template/domains/devtools/plugins/plan_validator_plugin.py +581 -0
  35. microcoreos/_template/domains/devtools/plugins/route_collision_linter_plugin.py +51 -0
  36. microcoreos/_template/domains/devtools/plugins/table_ownership_linter_plugin.py +66 -0
  37. microcoreos/_template/domains/devtools/plugins/tool_doc_drift_linter_plugin.py +74 -0
  38. microcoreos/_template/domains/system/plugins/event_delivery_monitor_plugin.py +53 -0
  39. microcoreos/_template/domains/system/plugins/system_events_plugin.py +132 -0
  40. microcoreos/_template/domains/system/plugins/system_events_stream_plugin.py +46 -0
  41. microcoreos/_template/domains/system/plugins/system_logs_stream_plugin.py +47 -0
  42. microcoreos/_template/domains/system/plugins/system_metrics_plugin.py +85 -0
  43. microcoreos/_template/domains/system/plugins/system_status_plugin.py +66 -0
  44. microcoreos/_template/domains/system/plugins/system_traces_plugin.py +128 -0
  45. microcoreos/_template/domains/system/plugins/system_traces_stream_plugin.py +121 -0
  46. microcoreos/_template/domains/system/plugins/tool_health_plugin.py +53 -0
  47. microcoreos/_template/extras/available_domains/chaos/plugins/blocking_boot_plugin.py +19 -0
  48. microcoreos/_template/extras/available_domains/chaos/plugins/chaos_control_plugin.py +625 -0
  49. microcoreos/_template/extras/available_domains/chaos/plugins/failing_plugin.py +27 -0
  50. microcoreos/_template/extras/available_domains/chaos/plugins/stress_plugin.py +40 -0
  51. microcoreos/_template/extras/available_domains/ping/plugins/ping_plugin.py +36 -0
  52. microcoreos/_template/extras/available_domains/scheduler/migrations/001_scheduler_one_shots.sql +8 -0
  53. microcoreos/_template/extras/available_domains/scheduler/models/scheduler_one_shot.py +15 -0
  54. microcoreos/_template/extras/available_domains/scheduler/plugins/durable_one_shots_plugin.py +125 -0
  55. microcoreos/_template/extras/available_domains/users/migrations/001_create_users.sql +12 -0
  56. microcoreos/_template/extras/available_domains/users/models/user.py +31 -0
  57. microcoreos/_template/extras/available_domains/users/plugins/create_user_plugin.py +89 -0
  58. microcoreos/_template/extras/available_domains/users/plugins/get_me_plugin.py +63 -0
  59. microcoreos/_template/extras/available_domains/users/plugins/login_plugin.py +107 -0
  60. microcoreos/_template/extras/available_domains/users/plugins/logout_plugin.py +26 -0
  61. microcoreos/_template/extras/available_tools/auth/auth_tool.py +132 -0
  62. microcoreos/_template/extras/available_tools/chaos/chaos_tool.py +32 -0
  63. microcoreos/_template/extras/available_tools/kafka/kafka_driver.py +452 -0
  64. microcoreos/_template/extras/available_tools/postgresql/postgresql_tool.py +882 -0
  65. microcoreos/_template/extras/available_tools/rabbitmq/rabbitmq_driver.py +303 -0
  66. microcoreos/_template/extras/available_tools/redis_state/redis_state_tool.py +253 -0
  67. microcoreos/_template/extras/available_tools/s3/__init__.py +0 -0
  68. microcoreos/_template/extras/available_tools/s3/s3_tool.py +426 -0
  69. microcoreos/_template/extras/available_tools/scheduler/scheduler_tool.py +328 -0
  70. microcoreos/_template/main.py +9 -0
  71. microcoreos/_template/plans/PILOT.md +99 -0
  72. microcoreos/_template/plans/README.md +236 -0
  73. microcoreos/_template/plans/active_plan.md +25 -0
  74. microcoreos/_template/plans/active_plan.yaml +43 -0
  75. microcoreos/_template/tools/config/config_tool.py +110 -0
  76. microcoreos/_template/tools/context/authoring_guide.md +305 -0
  77. microcoreos/_template/tools/context/context_tool.py +161 -0
  78. microcoreos/_template/tools/context/renderers.py +118 -0
  79. microcoreos/_template/tools/context/scanners.py +267 -0
  80. microcoreos/_template/tools/event_bus/drivers.py +108 -0
  81. microcoreos/_template/tools/event_bus/envelope.py +55 -0
  82. microcoreos/_template/tools/event_bus/event_bus_tool.py +506 -0
  83. microcoreos/_template/tools/event_bus/redis_streams_driver.py +300 -0
  84. microcoreos/_template/tools/event_bus/sqlite_driver.py +316 -0
  85. microcoreos/_template/tools/http_server/context.py +120 -0
  86. microcoreos/_template/tools/http_server/http_server_tool.py +558 -0
  87. microcoreos/_template/tools/http_server/pipeline.py +267 -0
  88. microcoreos/_template/tools/logger/logger_tool.py +96 -0
  89. microcoreos/_template/tools/sqlite/errors.py +152 -0
  90. microcoreos/_template/tools/sqlite/migrations.py +140 -0
  91. microcoreos/_template/tools/sqlite/sqlite_tool.py +591 -0
  92. microcoreos/_template/tools/sqlite/transaction.py +189 -0
  93. microcoreos/_template/tools/state/state_tool.py +173 -0
  94. microcoreos/_template/tools/system/registry_tool.py +90 -0
  95. microcoreos/_template/tools/telemetry/__init__.py +0 -0
  96. microcoreos/_template/tools/telemetry/telemetry_tool.py +143 -0
  97. microcoreos/base_plugin.py +20 -0
  98. microcoreos/base_tool.py +48 -0
  99. microcoreos/catalog.py +268 -0
  100. microcoreos/cli.py +206 -0
  101. microcoreos/container.py +222 -0
  102. microcoreos/context.py +6 -0
  103. microcoreos/kernel.py +223 -0
  104. microcoreos/project_readme.md +107 -0
  105. microcoreos/registry.py +53 -0
  106. microcoreos/scaffold.py +218 -0
  107. microcoreos/upgrade.py +547 -0
  108. microcoreos-0.1.0.dist-info/METADATA +366 -0
  109. microcoreos-0.1.0.dist-info/RECORD +112 -0
  110. microcoreos-0.1.0.dist-info/WHEEL +4 -0
  111. microcoreos-0.1.0.dist-info/entry_points.txt +2 -0
  112. microcoreos-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,176 @@
1
+ """Cache probe — proves whether an inference engine actually reuses the shared
2
+ prompt prefix (the "canonical executor prompt" of docs/PARALLEL_DEVELOPMENT.md).
3
+
4
+ It sends three requests and compares how many prompt tokens the engine REALLY
5
+ processed (not how many you sent):
6
+
7
+ 1. COLD shared prefix + task A -> baseline (everything processed)
8
+ 2. WARM identical prefix + task B -> should process ~task tokens only
9
+ 3. BROKEN prefix with 1 byte changed + B -> back to baseline (negative control)
10
+
11
+ PASS = warm processed-tokens << cold, and broken ~= cold.
12
+
13
+ The shared prefix is your real workload: AI_CONTEXT.md + plans/active_plan.yaml.
14
+
15
+ Usage:
16
+ python dev_infra/cache_probe.py --backend ollama --url http://localhost:11434 --model llama3.1
17
+ python dev_infra/cache_probe.py --backend llamacpp --url http://localhost:8080
18
+ python dev_infra/cache_probe.py --backend openai --url http://localhost:8000 --model my-model # vLLM etc.
19
+ python dev_infra/cache_probe.py --backend anthropic --model claude-haiku-4-5 # needs ANTHROPIC_API_KEY
20
+
21
+ No dependencies beyond the standard library.
22
+ """
23
+ import argparse
24
+ import json
25
+ import os
26
+ import time
27
+ import urllib.request
28
+
29
+ TASK_A = "Implement feature CreateThingPlugin from the plan above. Reply with only the file path you would create."
30
+ TASK_B = "Implement feature DeleteThingPlugin from the plan above. Reply with only the file path you would create."
31
+
32
+
33
+ def read_shared_prefix() -> str:
34
+ parts = []
35
+ for path in ("AI_CONTEXT.md", "plans/active_plan.yaml"):
36
+ try:
37
+ with open(path, "r", encoding="utf-8") as f:
38
+ parts.append(f"===== {path} =====\n{f.read()}")
39
+ except FileNotFoundError:
40
+ parts.append(f"===== {path} ===== (missing)")
41
+ parts.append("===== RULES =====\nOne file = one feature. Follow the plan exactly.")
42
+ return "\n\n".join(parts)
43
+
44
+
45
+ def post_json(url: str, body: dict, headers: dict) -> dict:
46
+ req = urllib.request.Request(
47
+ url,
48
+ data=json.dumps(body).encode("utf-8"),
49
+ headers={"Content-Type": "application/json", **headers},
50
+ method="POST",
51
+ )
52
+ with urllib.request.urlopen(req, timeout=600) as resp:
53
+ return json.loads(resp.read().decode("utf-8"))
54
+
55
+
56
+ # ── Backends ────────────────────────────────────────────────────────────────
57
+ # Each returns {"processed": int, "cached": int|None, "wall_s": float}
58
+ # "processed" = prompt tokens the engine actually evaluated this request.
59
+
60
+ def probe_ollama(url, model, prefix, task):
61
+ t0 = time.monotonic()
62
+ r = post_json(f"{url}/api/chat", {
63
+ "model": model, "stream": False,
64
+ "messages": [{"role": "user", "content": prefix + "\n\n" + task}],
65
+ "options": {"num_predict": 32},
66
+ }, {})
67
+ return {"processed": r.get("prompt_eval_count", -1), "cached": None,
68
+ "wall_s": time.monotonic() - t0}
69
+
70
+
71
+ def probe_llamacpp(url, model, prefix, task):
72
+ t0 = time.monotonic()
73
+ r = post_json(f"{url}/completion", {
74
+ "prompt": prefix + "\n\n" + task,
75
+ "n_predict": 32, "cache_prompt": True,
76
+ }, {})
77
+ timings = r.get("timings", {})
78
+ return {"processed": timings.get("prompt_n", -1), "cached": None,
79
+ "wall_s": time.monotonic() - t0}
80
+
81
+
82
+ def probe_openai(url, model, prefix, task):
83
+ """OpenAI-compatible servers (vLLM with prefix caching, OpenAI itself)."""
84
+ t0 = time.monotonic()
85
+ headers = {}
86
+ if os.getenv("OPENAI_API_KEY"):
87
+ headers["Authorization"] = f"Bearer {os.environ['OPENAI_API_KEY']}"
88
+ r = post_json(f"{url}/v1/chat/completions", {
89
+ "model": model, "max_tokens": 32,
90
+ "messages": [{"role": "user", "content": prefix + "\n\n" + task}],
91
+ }, headers)
92
+ usage = r.get("usage", {})
93
+ total = usage.get("prompt_tokens", -1)
94
+ cached = (usage.get("prompt_tokens_details") or {}).get("cached_tokens", 0)
95
+ return {"processed": total - cached, "cached": cached,
96
+ "wall_s": time.monotonic() - t0}
97
+
98
+
99
+ def _anthropic_headers():
100
+ h = {"anthropic-version": "2023-06-01"}
101
+ if os.getenv("ANTHROPIC_API_KEY"):
102
+ h["x-api-key"] = os.environ["ANTHROPIC_API_KEY"]
103
+ elif os.getenv("ANTHROPIC_AUTH_TOKEN"): # OAuth token (e.g. from ant auth / Claude Code)
104
+ h["Authorization"] = f"Bearer {os.environ['ANTHROPIC_AUTH_TOKEN']}"
105
+ h["anthropic-beta"] = "oauth-2025-04-20"
106
+ else:
107
+ raise SystemExit("Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN")
108
+ return h
109
+
110
+
111
+ def probe_anthropic(url, model, prefix, task):
112
+ t0 = time.monotonic()
113
+ r = post_json(f"{url}/v1/messages", {
114
+ "model": model, "max_tokens": 32,
115
+ "system": [{"type": "text", "text": prefix,
116
+ "cache_control": {"type": "ephemeral"}}],
117
+ "messages": [{"role": "user", "content": task}],
118
+ }, _anthropic_headers())
119
+ u = r.get("usage", {})
120
+ processed = u.get("input_tokens", -1) + u.get("cache_creation_input_tokens", 0)
121
+ return {"processed": processed, "cached": u.get("cache_read_input_tokens", 0),
122
+ "wall_s": time.monotonic() - t0}
123
+
124
+
125
+ BACKENDS = {
126
+ "ollama": (probe_ollama, "http://localhost:11434"),
127
+ "llamacpp": (probe_llamacpp, "http://localhost:8080"),
128
+ "openai": (probe_openai, "http://localhost:8000"),
129
+ "anthropic": (probe_anthropic, "https://api.anthropic.com"),
130
+ }
131
+
132
+
133
+ def main():
134
+ ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
135
+ ap.add_argument("--backend", choices=BACKENDS, required=True)
136
+ ap.add_argument("--model", default="")
137
+ ap.add_argument("--url", default="")
138
+ args = ap.parse_args()
139
+
140
+ probe, default_url = BACKENDS[args.backend]
141
+ url = (args.url or default_url).rstrip("/")
142
+ prefix = read_shared_prefix()
143
+ # Negative control: one byte changed at position 0 breaks the whole prefix.
144
+ broken_prefix = "!" + prefix[1:]
145
+
146
+ runs = [
147
+ ("COLD (prefix + task A)", prefix, TASK_A),
148
+ ("WARM (same prefix + task B)", prefix, TASK_B),
149
+ ("BROKEN (1 byte changed + task B)", broken_prefix, TASK_B),
150
+ ]
151
+ results = []
152
+ print(f"backend={args.backend} model={args.model or '(default)'} url={url}")
153
+ print(f"shared prefix: {len(prefix)} chars (~{len(prefix) // 4} tokens)\n")
154
+ for name, pfx, task in runs:
155
+ res = probe(url, args.model, pfx, task)
156
+ results.append(res)
157
+ cached = f" cached={res['cached']}" if res["cached"] is not None else ""
158
+ print(f"{name:38} processed={res['processed']:>7}{cached} wall={res['wall_s']:.2f}s")
159
+
160
+ cold, warm, broken = (r["processed"] for r in results)
161
+ print()
162
+ if 0 <= warm < cold * 0.5 and broken > cold * 0.5:
163
+ print(f"PASS — WARM processed only {warm}/{cold} tokens (cache hit), "
164
+ f"and BROKEN reprocessed {broken} (prefix rule confirmed).")
165
+ elif 0 <= warm < cold * 0.5:
166
+ print(f"PARTIAL — WARM hit the cache ({warm}/{cold}), but BROKEN also "
167
+ f"avoided reprocessing ({broken}) — check the negative control.")
168
+ else:
169
+ print(f"FAIL — WARM reprocessed {warm}/{cold} tokens. No prefix reuse: "
170
+ f"check that prefix caching is enabled on the server, the prefix "
171
+ f"is byte-identical, and (hosted) the prefix meets the minimum "
172
+ f"cacheable size for the model.")
173
+
174
+
175
+ if __name__ == "__main__":
176
+ main()
@@ -0,0 +1,137 @@
1
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2
+ # MicroCoreOS — Development Infrastructure
3
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
4
+ #
5
+ # Uso:
6
+ # docker compose -f dev_infra/docker-compose.yml up -d
7
+ # docker compose -f dev_infra/docker-compose.yml down
8
+ #
9
+ # Los datos persisten en volúmenes Docker entre reinicios.
10
+ # Para borrar todo y empezar limpio:
11
+ # docker compose -f dev_infra/docker-compose.yml down -v
12
+ #
13
+ # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
14
+
15
+ services:
16
+
17
+ # ─── PostgreSQL ───────────────────────────────────────────
18
+ postgres:
19
+ image: postgres:17-alpine
20
+ container_name: microcoreos_postgres
21
+ restart: unless-stopped
22
+ ports:
23
+ - "5432:5432"
24
+ environment:
25
+ POSTGRES_USER: postgres
26
+ POSTGRES_PASSWORD: postgres
27
+ POSTGRES_DB: microcoreos
28
+ volumes:
29
+ - postgres_data:/var/lib/postgresql/data
30
+ healthcheck:
31
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
32
+ interval: 5s
33
+ timeout: 3s
34
+ retries: 5
35
+
36
+ # ─── Redis ────────────────────────────────────────────────
37
+ redis:
38
+ image: redis:7-alpine
39
+ container_name: microcoreos_redis
40
+ restart: unless-stopped
41
+ ports:
42
+ - "6379:6379"
43
+ volumes:
44
+ - redis_data:/data
45
+ healthcheck:
46
+ test: ["CMD", "redis-cli", "ping"]
47
+ interval: 5s
48
+ timeout: 3s
49
+ retries: 5
50
+
51
+ # ─── RabbitMQ ─────────────────────────────────────────────
52
+ rabbitmq:
53
+ image: rabbitmq:3.13-alpine
54
+ container_name: microcoreos_rabbitmq
55
+ restart: unless-stopped
56
+ ports:
57
+ - "5672:5672"
58
+ - "15672:15672"
59
+ environment:
60
+ RABBITMQ_DEFAULT_USER: guest
61
+ RABBITMQ_DEFAULT_PASS: guest
62
+ volumes:
63
+ - rabbitmq_data:/var/lib/rabbitmq
64
+ healthcheck:
65
+ test: ["CMD", "rabbitmq-diagnostics", "ping"]
66
+ interval: 10s
67
+ timeout: 5s
68
+ retries: 5
69
+
70
+ # ─── Kafka (KRaft, single node) ───────────────────────────
71
+ kafka:
72
+ image: apache/kafka:3.8.0
73
+ container_name: microcoreos_kafka
74
+ restart: unless-stopped
75
+ ports:
76
+ - "9092:9092"
77
+ environment:
78
+ # Single-node KRaft: the container is broker AND controller.
79
+ KAFKA_NODE_ID: 1
80
+ KAFKA_PROCESS_ROLES: broker,controller
81
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9093
82
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
83
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
84
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
85
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
86
+ KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
87
+ volumes:
88
+ - kafka_data:/var/lib/kafka/data
89
+ healthcheck:
90
+ test: ["CMD-SHELL", "/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server localhost:9092 >/dev/null 2>&1"]
91
+ interval: 10s
92
+ timeout: 10s
93
+ retries: 5
94
+
95
+ # ─── RustFS (S3-compatible object storage) ────────────────
96
+ rustfs:
97
+ image: rustfs/rustfs:latest
98
+ container_name: microcoreos_rustfs
99
+ restart: unless-stopped
100
+ ports:
101
+ - "9000:9000"
102
+ - "9001:9001"
103
+ environment:
104
+ RUSTFS_ACCESS_KEY: minioadmin
105
+ RUSTFS_SECRET_KEY: minioadmin
106
+ volumes:
107
+ - rustfs_data:/data
108
+ command: server /data --console-address ":9001"
109
+ healthcheck:
110
+ test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
111
+ interval: 30s
112
+ timeout: 10s
113
+ retries: 3
114
+
115
+ # ─── Jaeger (OpenTelemetry / OTLP tracing UI) ─────────────
116
+ # UI: http://localhost:16686
117
+ # App: en .env → OTEL_ENABLED=true y
118
+ # OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
119
+ # Requiere: uv add opentelemetry-sdk opentelemetry-exporter-otlp
120
+ # Almacenamiento en memoria (dev): las trazas se pierden al reiniciar.
121
+ jaeger:
122
+ image: jaegertracing/all-in-one:1.60
123
+ container_name: microcoreos_jaeger
124
+ restart: unless-stopped
125
+ ports:
126
+ - "16686:16686" # Web UI
127
+ - "4317:4317" # OTLP gRPC (el que usa el tool telemetry)
128
+ - "4318:4318" # OTLP HTTP
129
+ environment:
130
+ COLLECTOR_OTLP_ENABLED: "true"
131
+
132
+ volumes:
133
+ postgres_data:
134
+ redis_data:
135
+ rabbitmq_data:
136
+ kafka_data:
137
+ rustfs_data:
@@ -0,0 +1,187 @@
1
+ # The `microcoreos` command
2
+
3
+ Installed by the package. Five commands: create a project, install an extra,
4
+ see what changed upstream, boot, boot with reload.
5
+
6
+ ```
7
+ microcoreos new <path> [--force] [--no-ai-kit] Materialize a new project
8
+ microcoreos add <extra> [--no-install] Install an extra completely
9
+ microcoreos upgrade [--apply] Report/apply upstream changes
10
+ microcoreos [run] [--boot-tool <tool>] Boot the Kernel
11
+ microcoreos dev Boot with auto-reload
12
+ ```
13
+
14
+ Except for `new`, run them from the root of a project — the directory holding
15
+ `tools/`, `domains/` and `plans/`. Running elsewhere is refused rather than
16
+ booting an empty system and calling it Ready.
17
+
18
+ ---
19
+
20
+ ## `microcoreos new <path>`
21
+
22
+ Copies the framework's source into your directory: `tools/`, `domains/system`,
23
+ `domains/devtools`, `extras/`, `plans/`, `dev_infra/`, `main.py`, `Dockerfile`,
24
+ `.env.example` — plus the AI kit (`AGENTS.md`, `INSTRUCTIONS_FOR_AI.md`,
25
+ `.agent/`, `docs/`).
26
+
27
+ One list decides all of it: `scaffold.RUNTIME_ENTRIES`. The wheel's copy under
28
+ `_template/` is generated from that same list by `hatch_build.py`, so what a
29
+ checkout copies and what the package ships cannot disagree.
30
+
31
+ **Why copied instead of imported.** Installing and swapping infrastructure here
32
+ IS file placement (`mv extras/available_tools/postgresql tools/`, dropping a
33
+ `{name}_driver.py` into `tools/event_bus/`). None of that works against
34
+ `site-packages`: it may be read-only, and anything written there is wiped by
35
+ the next upgrade. Only the Kernel — `microcoreos/` itself — stays in the
36
+ package.
37
+
38
+ It also writes, only when absent: `.env` (from `.env.example`),
39
+ `pyproject.toml`, and a `README.md` for the project. An existing one is never
40
+ overwritten, so `uv init && uv add microcoreos && microcoreos new .` works.
41
+
42
+ | Flag | Effect |
43
+ |---|---|
44
+ | `--force` | Materialize even if `tools/` or `domains/` already exist |
45
+ | `--no-ai-kit` | Skip `AGENTS.md`, `INSTRUCTIONS_FOR_AI.md`, `.agent/`, `docs/` |
46
+
47
+ **Not materialized:** `ping` (the hello-world) and auth. A fresh project has no
48
+ `tools/auth` and no `domains/users` — no users table, no JWT, and no
49
+ `AUTH_SECRET_KEY` to set before it will boot. `http_server_tool` takes
50
+ `auth_validator` as an optional callback and never imports auth, so nothing
51
+ misses it.
52
+
53
+ `microcoreos add auth` installs it: the tool, plus the four plugins that ARE
54
+ auth — `create_user`, `login`, `get_me`, `logout` — with the model and the
55
+ migration. The CRUD around them (list, get-by-id, update, delete) is not
56
+ shipped: that is what you write for your own entities.
57
+
58
+ ---
59
+
60
+ ## `microcoreos add <extra>`
61
+
62
+ Activating an extra is three acts, and skipping any one fails somewhere
63
+ different:
64
+
65
+ | Act | Skipping it |
66
+ |---|---|
67
+ | Install the optional dependency | `No module named 'asyncpg'` at boot |
68
+ | Move the source into `tools/`/`domains/` | Nothing happens — discovery only sees those directories |
69
+ | Add its settings to `.env` | Boots, then connects nowhere |
70
+
71
+ This does all three:
72
+
73
+ ```bash
74
+ microcoreos add postgres
75
+ ```
76
+
77
+ ```
78
+ 📦 Installing extra 'postgres'
79
+
80
+ $ uv add 'microcoreos[postgres]'
81
+ ✓ extras/available_tools/postgresql → tools/postgresql
82
+ ✓ .env += PG_HOST, PG_PORT, PG_USER, PG_PASSWORD, PG_DATABASE
83
+ ```
84
+
85
+ Available: `auth`, `ping`, `postgres`, `redis`, `s3`, `scheduler`, `kafka`,
86
+ `rabbitmq`, `chaos`. Run `microcoreos add` with no argument to list them.
87
+
88
+ `auth` and `ping` are the two that need no external service. `auth` is an extra
89
+ because a users table, a roles model and a JWT flavour are not something a
90
+ framework should impose; `ping` is a single `GET /ping` you read for the shape
91
+ of a minimal plugin and then delete.
92
+
93
+ Three rules it follows:
94
+
95
+ - **The tool moves before its domain.** A plugin cannot exist without the tool
96
+ it asks for, so the other order leaves a boot that aborts the plugin.
97
+ - **Drivers are not tools.** `kafka` and `rabbitmq` do not become
98
+ `tools/kafka/`; the `*_driver.py` drops into `tools/event_bus/` and `.env`
99
+ gets `EVENT_BUS_DRIVER=kafka`.
100
+ - **`.env` is never overwritten.** A value already there is your decision.
101
+ Re-running `add` is a no-op.
102
+
103
+ `uv add` runs only when there is a `pyproject.toml` and `uv` is on PATH —
104
+ otherwise the command is printed for you to run. `--no-install` skips that step.
105
+
106
+ ---
107
+
108
+ ## `microcoreos upgrade`
109
+
110
+ The mitigation for honest vendoring: your tools and domains are your source, so
111
+ a fix upstream does not reach them on its own.
112
+
113
+ `new` records the SHA-256 of every file it writes in
114
+ `.microcoreos/manifest.json`. That baseline is what makes three otherwise
115
+ indistinguishable states distinguishable:
116
+
117
+ | local | upstream | verdict |
118
+ |---|---|---|
119
+ | = baseline | changed | **safe to update** — you never touched it |
120
+ | changed | = baseline | **yours** — left alone, always |
121
+ | changed | changed | **conflict** — reported, never written |
122
+ | = baseline | withdrawn | **deleted** — the framework's file to take back |
123
+ | changed | withdrawn | **released** — kept, and no longer tracked at all |
124
+
125
+ ```bash
126
+ microcoreos upgrade # report only (default)
127
+ microcoreos upgrade --apply # write the safe ones, move the baseline
128
+ ```
129
+
130
+ `--apply` never touches a conflict and never resurrects a file you deleted —
131
+ removing a tool you do not use is a supported act, not damage.
132
+
133
+ **A file dropped upstream is deleted here, unless you edited it.** The rule is
134
+ the same one as everywhere else: untouched is the framework's to withdraw, and
135
+ an empty `tools/<name>/` goes with the last file in it. If you did edit it, it
136
+ stays and leaves the baseline for good — upstream no longer ships it and you
137
+ changed it, so there is nothing left to compare, and re-reporting it on every
138
+ run would just teach you to skip the output.
139
+
140
+ If most of the baseline appears to have vanished, nothing is deleted: that is
141
+ what a partial or broken install looks like, not what a release looks like.
142
+
143
+ **The manifest belongs in version control.** The scaffolded `.gitignore` does
144
+ not exclude it on purpose: a teammate who clones the project without it cannot
145
+ upgrade, because their edits would be indistinguishable from stale files.
146
+ Without a manifest the command refuses outright — it is written by
147
+ `microcoreos new`, and its absence is an error rather than a degraded mode.
148
+
149
+ **Extras you installed are tracked to where they moved.** `microcoreos add`
150
+ records the move, and the baseline stays keyed by the file's upstream path —
151
+ so a fix to `extras/available_tools/scheduler/` reaches `tools/scheduler/`
152
+ where you actually put it.
153
+
154
+ Moving a folder yourself works too, including to a name no convention could
155
+ guess (`mv extras/available_tools/postgresql tools/my-db`). Names cannot follow
156
+ that, so content does: an unedited file still hashes to its baseline digest
157
+ wherever it now sits, and the move is written into the manifest the first time
158
+ `upgrade` runs — after which the folder is tracked by name and you can edit it
159
+ freely. A digest matching more than one candidate proves nothing and is
160
+ ignored, so nothing is claimed on a guess.
161
+
162
+ ---
163
+
164
+ ## `microcoreos run` / `microcoreos dev`
165
+
166
+ `microcoreos` alone is `microcoreos run`. Identical to `uv run main.py` —
167
+ the root `main.py` is a shim over this same code path, so both are the same
168
+ boot.
169
+
170
+ ```bash
171
+ microcoreos # boot
172
+ microcoreos run --boot-tool db # boot ONE tool in isolation, then exit
173
+ microcoreos dev # boot, reload on .py changes
174
+ ```
175
+
176
+ `--boot-tool` is the migrations pipeline entry point:
177
+ `DB_AUTO_MIGRATE=true microcoreos run --boot-tool db` — see
178
+ [ELASTIC_DEPLOYMENT.md](ELASTIC_DEPLOYMENT.md).
179
+
180
+ `dev` needs `watchfiles` (`uv add --dev watchfiles`); it says so if missing.
181
+
182
+ **Two things the command does that `python main.py` gets for free.** A console
183
+ script starts with the venv's `bin/` as `sys.path[0]`, not your project, so the
184
+ project root is inserted explicitly — without it every `import tools.*` fails
185
+ and the Kernel reports an empty system as Ready. And `.env` is loaded by
186
+ explicit path, because bare `load_dotenv()` searches upward from its *caller*,
187
+ which installed is `site-packages`, not your project.