Note
LLM/KI-Kontext-Index: Eine maschinenlesbare Spezifikation und Architektur-Übersicht für KI-Agenten befindet sich in llms.txt.
Namensmigration: Das Projekt und das kanonische Python-Paket heißen
COMAbzw.coma. Der frühere NameCOMAS/comasbleibt für eine Übergangsphase als Import- und CLI-Alias verfügbar; neue Integrationen müssencomaverwenden.
Die Lebenszyklus-Schicht für Agenten: Wie entsteht ein Agent als eigener Prozess, und wie bleibt man mit ihm in Kontakt, solange er läuft?
COMA steht primär als session-übergreifender und session-unabhängiger Kommunikations- und Auftragskanal für Agents (Dateisystem-basiert via IN/, OUT/, DONE/). Als Begriffsauslegung bietet sich Command / Communication for Agents an (mit Alternativ-Deutungen wie agent spawner oder agent cloner, ohne den Paket- und Repository-Namen coma zu ändern).
Genau eine Verantwortung. COMA sperrt nichts, verwaltet keine Rechte und hält kein Gedächtnis. Es arbeitet mit Dateien und Prozessen — ohne Konto, ohne Netz, ohne Cluster. Null Abhängigkeiten, nur Standardbibliothek.
Konzept, Begründung und Abgrenzung: KONZEPT.md.
flowchart TD
subgraph Client ["Orchestrator / Client-Session"]
A[JobBoard.submit] -->|Schreibt Auftrag Prompt| B["IN/<jobid>.md"]
end
subgraph COMA ["COMA Agent Spawner"]
B --> C{JobRunner / Spawner}
C -->|Wählt CLI Adapter| D[Claude / Codex / AGY / Kimi Adapter]
D -->|Startet Subprozess| E[Lokale Agent-Ausführung]
end
subgraph Agent ["Agent Execution"]
E -->|Fortschritts-Events| F["OUT/coma.<jobid>.from-agent.jsonl"]
E -->|Schreibt Endergebnis| G["OUT/<jobid>.result.md"]
end
subgraph Completion ["Abschluss"]
G --> H["OUT/coma.<jobid>.json (Status: DONE)"]
H --> I["Verschiebt IN/<jobid>.md -> DONE/<jobid>.md"]
end
| Schicht | Frage | Zuständig |
|---|---|---|
| Lebenszyklus | Wie entsteht ein Agent, wie rede ich mit ihm? | COMA |
| Anspruch | Wer darf was anfassen? | team-lock → lock-master → Roshambo |
| Gedächtnis | Wurde das schon versucht, wie ging es aus? | Roshambo |
Die Verben trennen sauber: COMA spricht spawn, send, poll, result. Ein Koordinator spricht claim, release, remember, recall, decide, status. Keine Überschneidung.
COMA dient primär als entkoppelter, session-unabhängiger Kommunikations- und Auftragskanal für Agents. Es erlaubt Orchestratoren und Sitzungen, Aufträge strukturiert abzulegen, Agenten-Prozesse zu steuern und Ergebnisse abzufragen, ohne an die Laufzeit einer einzelnen interaktiven Session gebunden zu sein.
-
Historischer erster Use Case: Entkopplung von Remote-Control-Sessions In einer Remote-Control-Session reicht Claude Code
--dangerously-skip-permissionsnicht an den Remote-Client durch (offene Issues #71518, #29214). Folge: Jeder Tool-Aufruf fragt nach — auch bei Agenten. Ist niemand am Rechner, stehen sie still: unbeaufsichtigte Agenten warten auf Klicks, die niemand gibt.Die Lösung ist, den Agenten gar nicht erst in der RC-Session leben zu lassen: ein eigener lokaler Prozess außerhalb dieser Session, Kommunikation über das Dateisystem. COMA umgeht keine Sicherheitsgrenze, sondern nutzt dokumentierte CLI-Flags (
--permission-mode,--allowedToolsund Verwandte) an der Stelle, an der sie tatsächlich ankommen. -
Session-übergreifende Handoffs & Multi-Agent Relays Aufgaben werden über Session-Grenzen hinweg zwischen Agents übergeben, ohne dass Prozesse blockieren oder Kontext verloren geht.
-
Hintergrund-Auftragsverarbeitung Dateisystem-basierte Steuerung (
IN/,OUT/,DONE/) für autonome Hintergrund-Läufer und entkoppelte Tool-Ausführungen.
from coma import JobBoard, JobRunner
board = JobBoard(r"C:\Users\du\_agentjobs")
board.submit("meinjob", "# Auftrag\n\nSchreibe das Ergebnis nach OUT/meinjob.result.md.\n")
result = JobRunner(board).run("meinjob")
print(result["status"]["state"], result["result_written"])Oder von der Kommandozeile:
coma --root C:\Users\du\_agentjobs run meinjob
coma --root C:\Users\du\_agentjobs run meinjob --dry-run :: nur zeigen, nichts starten
coma --root C:\Users\du\_agentjobs status meinjob
coma --root C:\Users\du\_agentjobs result meinjob--dry-run zuerst. Es baut das vollständige Kommando und zeigt es, ohne dass ein Token fließt.
IN/ <jobid>.md Auftrag (Freitext-Markdown)
OUT/ <jobid>.result.md Ergebnis
coma.<jobid>.json Status — nur der Runner schreibt
coma.<jobid>.from-agent.jsonl Fortschritt — nur der Agent schreibt
coma.<jobid>.to-agent.jsonl Nachrichten — nur der Orchestrator schreibt
coma.<jobid>.console.log stdout/stderr des Laufs
DONE/ <jobid>.md erledigter Auftrag
Ein Schreiber je Datei — kein Locking nötig, Kollision strukturell unmöglich. Gelesen werden darf von allen. Das ist keine Vorsichtsmaßnahme, sondern eine Lektion: Eine geteilte Logdatei in OneDrive hat schon einmal zu Konfliktkopien geführt.
.jsonl für die Kanäle, weil Anhängen atomar ist — ein Schreiber muss nicht erst lesen, parsen und neu schreiben.
Die Auftragsdatei ist freies Markdown und enthält den vollständigen Prompt. Der Agent bekommt nur einen Zeiger darauf; er liest die Datei selbst und schreibt sein Ergebnis selbst als Datei. Damit hängt die Rückgabe weder an der stdout-Größe noch am Encoding.
Eine RC-Session kann Aufträge schreiben und Ergebnisse abholen, ohne den Agenten selbst zu hosten:
from coma import JobBoard, read_result, wait_for_finish
board = JobBoard(root)
paths = board.submit("meinjob", auftrag_markdown) # 1. Auftrag schreiben
# 2. Lokal läuft irgendwann: coma run meinjob
status = wait_for_finish(paths, timeout=3600) # 3. Statusdatei beobachten
if status["exit_code"] == 0:
print(read_result(paths))wait_for_finish beobachtet die Statusdatei, nicht den Prozess. Genau deshalb funktioniert es aus einem fremden Prozess, einer anderen Session oder über OneDrive von einem anderen Rechner.
from coma import from_agent, to_agent
to_agent(paths).append({"kind": "hint", "text": "Nimm Variante B."}) # Orchestrator
for record in from_agent(paths).read(): # Agent-Meldungen
print(record)Der Rollen-Parameter ist Dokumentation mit Zähnen: to_agent(paths).append(…, role="agent") wirft, statt eine Konfliktkopie zu erzeugen.
Ein Adapter weiß genau zwei Dinge: wie das Kommando für seine CLI aussieht und welche Umgebung sie braucht. Er startet nichts — das macht der Spawner. Deshalb ist der Kommandobau ohne Prozessstart prüfbar.
from coma import ClaudeAdapter, Spawner
adapter = ClaudeAdapter(model="sonnet", permission_mode="dontAsk",
allowed_tools=["Read", "Write"], max_budget_usd=2.0)
print(adapter.build_cmd("Sag Hallo")) # nur die Argumentliste, nichts läuft
spawner = Spawner(adapter)
result = spawner.run("Sag Hallo", log_file="lauf.log")
handle = spawner.start("Sag Hallo", log_file="lauf.log") # nicht blockierend
while handle.poll() is None:
...| Adapter | Ziel | Stand |
|---|---|---|
claude |
Claude Code CLI | verifiziert — Flags gegen claude --help 2.1.220 geprüft, echter Durchlauf belegt |
codex |
native codex exec |
Verifiziert — CLI 0.145.0, read-only/workspace-write, Ergebnisdatei via --output-last-message |
agy |
Antigravity/Gemini | Verifiziert — agy 1.1.7, stdout und Exitcode live geprüft; Job-Ergebnisdatei bleibt kanonisch |
kimi |
Kimi Code CLI | Gerüst — CLI 0.29.2 gefunden; der COMA-Adapter bleibt unverified, bis ein eigener Prompt-Lauf belegt ist |
verified ist keine Kosmetik: Der Spawner weigert sich, einen Gerüst-Adapter zu starten, solange nicht ausdrücklich allow_unverified=True gesetzt ist. So ist Adapterwissen dokumentiert und getestet, ohne dass ein ungetesteter Aufrufweg unbemerkt in einen unbeaufsichtigten Lauf gerät.
coma adapters :: zeigt Stand, gefundene Binary und die Fallstricke je AdapterdontAsk und bypassPermissions sind verschiedene Sicherheitsprofile, nicht zwei Namen für dasselbe:
dontAskfragt nie, sondern verweigert. Zusammen mit einer expliziten Werkzeugliste kann ein Agent damit strukturell nicht hängenbleiben. Für unbeaufsichtigte Läufe die bessere Wahl — und deshalb der Standard.bypassPermissionskann in Sonderfällen weiterhin nachfragen. In einer RC-Session heißt das: Der Agent steht, bis jemand klickt.
Drei benannte Profile, jedes mit belegter Herkunft:
ClaudeAdapter.preset("unattended") # Standard: dontAsk + explizite Werkzeugliste
ClaudeAdapter.preset("read_only") # dontAsk + Read,Glob,Grep
ClaudeAdapter.preset("bat_compat") # die verifizierte Startschale: bypassPermissions--tools begrenzt, welche Built-ins überhaupt existieren; --allowedTools gibt sie vorab frei. Verschiedene Dinge, deshalb zwei Parameter:
ClaudeAdapter(allowed_tools=["Read"], available_tools=["Read", "Bash"])
# --tools Read,Bash --allowedTools Read
# -> Bash ist da, aber nicht freigegeben: unter dontAsk wird es verweigert.| Wert | Wirkung |
|---|---|
["Read", "Write"] |
Liste, komma-verbunden als ein Argument |
MIRROR (Standard für available_tools) |
spiegelt allowed_tools |
NO_RESTRICTION / None |
Flag entfällt ganz |
[] bei available_tools |
--tools "" — alle Built-ins abschalten |
MCP lässt sich über --tools nicht abschalten; dafür gibt es --disallowedTools mcp__*, standardmäßig gesetzt (allow_mcp=True hebt es auf).
COMA sperrt nichts. Es ruft Claims über eine schmale Schnittstelle auf, nicht gegen ein konkretes Modul:
comalock= COMA +lock-master(lokal, offline)comaroshambo= COMA + Roshambo (verteilt, Cloud)
from coma import LockBackend, claimed # LockBackend ist ein Protocol
with claimed(mein_backend, "pfad/zum/projekt", kind="project"):
JobRunner(board).run("meinjob")Der Standard ist NullLock — gewährt alles, merkt sich nichts.
| Befehl | Zweck |
|---|---|
run [jobid] |
Job starten (Ersatz für START-LOCAL-AGENT.bat); ohne ID der älteste |
run … --dry-run |
Kommando bauen und zeigen, nichts starten |
cmd <prompt> |
Kommando für einen freien Prompt zeigen |
submit <jobid> |
Auftrag in IN/ ablegen (--file oder stdin) |
status <jobid> · list |
Zustand eines Jobs bzw. aller Jobs |
result <jobid> · log <jobid> |
Ergebnisdatei bzw. Konsolenlog ausgeben |
send <jobid> <text> · inbox <jobid> |
Nachricht an den Agenten bzw. dessen Meldungen |
adapters |
Adapter, Stand, gefundene Binary, Fallstricke |
check · vendor |
Manifest prüfen bzw. schreiben |
--json gibt es überall, --root bestimmt das Jobverzeichnis.
python -m pytest -q :: 241 TestsKein Test startet einen echten Prozess. subprocess wird überall ersetzt; das ist Absicht — ein Test, der claude startet, kostet Tokens und braucht Netz. Geprüft wird der Kommandobau gegen erwartete Argumentlisten.
COMA ist integraler Bestandteil der ellmos-ai Orchestrierungsarchitektur und des übergeordneten Open-Source-Dachverbunds open-bricks:
| Schicht / Ökosystem | Repository | Zweck |
|---|---|---|
| Agent-Orchestrierung | ellmos-ai/coma |
Lebenszyklus-Spawner & entkoppeltes dateibasiertes Job-Board |
| Governance & Policies | ellmos-ai/policy-registry |
Zentrale Richtlinien-, Rollen- und Delegationsverwaltung |
| Datentransit | ellmos-ai/sqlite-transit-sync |
Zero-Copy Transaktions- und SQLite-Replikationssynchronisation |
| System-Inspektion | ellmos-ai/system-explorer |
System-Introspektion, MCP-Diagnose & Flotten-Monitoring |
| Automationsfluss | ellmos-ai/workflowhooker |
Ereignis-Trigger, Pipeline-Hooks & Webhook-Orchestrierung |
| Aufgabendelegation | ellmos-ai/ellmos-delegation-authority |
Autonome Aufgabenautorisierung & Vollmachtenprüfung |
| Entwickler-Hub | dev-bricks/DevCenter |
Entwickler-Workstation-Hub, Arbeitsbereichsverwaltung & Tools |
| CLI Sandbox | dev-bricks/CodeBox |
Isolierte Codeausführung & Snippet-Validierung |
| Agenten-Bootstrap | dev-bricks/safe-start-for-codex |
Sichere Initialisierung, Umgebungschecks & Preflight-Diagnostik |
Details zur Prozessisolierung, den Protokollgrenzen nach dem Single-Writer-Prinzip und Richtlinien zur Offenlegung von Schwachstellen finden sich in SECURITY.md.
Version 0.2.1. Lizenz: MIT. Das Quellrepository gehört zum ellmos-ai / open-bricks Ökosystem.