@andespindola/crow-skills 0.38.2 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/crow_skills/__init__.py +1 -1
- package/crow_skills/cli.py +53 -0
- package/crow_skills/demands.py +195 -0
- package/package.json +1 -1
- package/release.json +11 -6
- package/skills/crow-behaviour/SKILL.md +2 -0
package/crow_skills/__init__.py
CHANGED
package/crow_skills/cli.py
CHANGED
|
@@ -8,6 +8,7 @@ import sys
|
|
|
8
8
|
from . import __version__
|
|
9
9
|
from . import autoupdate
|
|
10
10
|
from . import bots
|
|
11
|
+
from . import demands as demands_mod
|
|
11
12
|
from . import broadcast
|
|
12
13
|
from . import events
|
|
13
14
|
from . import groups as groups_mod
|
|
@@ -75,6 +76,10 @@ COMMANDS = {
|
|
|
75
76
|
["crow-skills mode", "crow-skills mode off", "crow-skills mode on"]),
|
|
76
77
|
"events": ("Barramento de eventos dos hooks (crow-event/1) para terminais.",
|
|
77
78
|
["crow-skills events", "crow-skills events tail --limit 20", "crow-skills events tail --follow"]),
|
|
79
|
+
"demand": ("Demandas do dono que ficam registradas até alguém assumir — sem validade, porque demanda sem resposta não é velha, é pendente.",
|
|
80
|
+
["crow-skills demand list", "crow-skills demand list --pending",
|
|
81
|
+
"crow-skills demand add \"arrumar o scroll do painel\"",
|
|
82
|
+
"crow-skills demand take 9f2a1c", "crow-skills demand done 9f2a1c \"publicado em 0.2.26\""]),
|
|
78
83
|
"bot": ("Bots especialistas independentes: criar sem bloquear, reaproveitar o ocioso, emprestar entre sessões.",
|
|
79
84
|
["crow-skills bot list", "crow-skills bot spawn --role revisor-sql --brief \"revise o diff de migrations\"",
|
|
80
85
|
"crow-skills bot assign 9f2a1c \"agora valide o rollback\"", "crow-skills bot release 9f2a1c",
|
|
@@ -209,6 +214,13 @@ def parser():
|
|
|
209
214
|
child.add_argument("action", nargs="?", default="snapshot", choices=("snapshot", "tail"))
|
|
210
215
|
child.add_argument("--limit", type=int, default=50, help="tail: quantos eventos (padrão 50; 0 = todos os retidos)")
|
|
211
216
|
child.add_argument("--follow", action="store_true", help="tail: transmitir eventos novos em JSONL até Ctrl-C")
|
|
217
|
+
child = command_parser(sub, "demand", [common])
|
|
218
|
+
child.add_argument("action", nargs="?", default="list",
|
|
219
|
+
choices=("list", "add", "take", "done", "fail", "release"))
|
|
220
|
+
child.add_argument("target", nargs="?", help="take/done/fail/release: id da demanda")
|
|
221
|
+
child.add_argument("text", nargs="*", help="add: o pedido; done/fail: a nota de desfecho")
|
|
222
|
+
child.add_argument("--pending", action="store_true", help="list: só o que ninguém assumiu")
|
|
223
|
+
child.add_argument("--thread", help="add: id da conversa do broadcast que originou o pedido")
|
|
212
224
|
child = command_parser(sub, "bot", [common])
|
|
213
225
|
child.add_argument("action", nargs="?", default="list",
|
|
214
226
|
choices=("list", "spawn", "assign", "release", "stop", "prune", "learned", "knowledge"))
|
|
@@ -718,6 +730,45 @@ def orphans_command(args, home, interactive):
|
|
|
718
730
|
return data
|
|
719
731
|
|
|
720
732
|
|
|
733
|
+
def demand_command(args, home):
|
|
734
|
+
"""The owner's queue. `list` is the default action because the first thing
|
|
735
|
+
an agent should do is look at what is already waiting."""
|
|
736
|
+
action = args.action
|
|
737
|
+
cwd = os.path.abspath(getattr(args, "cwd", None) or os.getcwd())
|
|
738
|
+
if action == "list":
|
|
739
|
+
live = {s.get("key") for s in broadcast.live_sessions(home)}
|
|
740
|
+
rows = demands_mod.listing(home, pending_only=getattr(args, "pending", False), live_keys=live)
|
|
741
|
+
return {"demands": rows, "text": demands_mod.render(rows)}
|
|
742
|
+
if action == "add":
|
|
743
|
+
# `target` is positional, so it swallows the first word of the demand;
|
|
744
|
+
# for add there is no id, and the whole tail is the request.
|
|
745
|
+
parts = ([getattr(args, "target", None)] if getattr(args, "target", None) else []) + list(getattr(args, "text", []) or [])
|
|
746
|
+
text = " ".join(p for p in parts if p).strip()
|
|
747
|
+
if not text:
|
|
748
|
+
raise CrowError("usage", "add precisa do pedido: crow-skills demand add \"o que fazer\".")
|
|
749
|
+
try:
|
|
750
|
+
record = demands_mod.add(home, text, thread=getattr(args, "thread", None))
|
|
751
|
+
except ValueError as exc:
|
|
752
|
+
raise CrowError("usage", str(exc))
|
|
753
|
+
return {"demand": record,
|
|
754
|
+
"text": "demanda " + record["id"] + " registrada e pendente; ela não expira."}
|
|
755
|
+
if not getattr(args, "target", None):
|
|
756
|
+
raise CrowError("usage", action + " precisa do id da demanda.")
|
|
757
|
+
note = " ".join(getattr(args, "text", []) or []).strip()
|
|
758
|
+
try:
|
|
759
|
+
if action == "take":
|
|
760
|
+
owner = broadcast_owner(args, home, cwd)
|
|
761
|
+
record = demands_mod.take(home, args.target, by=(owner or {}).get("key"))
|
|
762
|
+
return {"demand": record, "text": "demanda " + record["id"] + " é sua; devolva com release se não puder terminar."}
|
|
763
|
+
if action in ("done", "fail"):
|
|
764
|
+
record = demands_mod.close(home, args.target, "done" if action == "done" else "failed", note)
|
|
765
|
+
return {"demand": record, "text": "demanda " + record["id"] + " encerrada como " + record["state"] + "."}
|
|
766
|
+
record = demands_mod.release(home, args.target)
|
|
767
|
+
return {"demand": record, "text": "demanda " + record["id"] + " devolvida à fila."}
|
|
768
|
+
except (ValueError, RuntimeError) as exc:
|
|
769
|
+
raise CrowError("usage", str(exc))
|
|
770
|
+
|
|
771
|
+
|
|
721
772
|
def bot_command(args, home):
|
|
722
773
|
"""Independent specialists. `list` comes first on purpose: the rule is to
|
|
723
774
|
look for an idle bot before creating another one."""
|
|
@@ -1364,6 +1415,8 @@ def run(args):
|
|
|
1364
1415
|
return mcp_command(args, home)
|
|
1365
1416
|
if args.command == "events":
|
|
1366
1417
|
return events_command(args, home, store)
|
|
1418
|
+
if args.command == "demand":
|
|
1419
|
+
return demand_command(args, home)
|
|
1367
1420
|
if args.command == "bot":
|
|
1368
1421
|
return bot_command(args, home)
|
|
1369
1422
|
if args.command == "broadcast":
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""The owner's demands, kept until somebody takes them.
|
|
2
|
+
|
|
3
|
+
A demand typed in the Crow Cockpit used to exist only as a broadcast message,
|
|
4
|
+
and broadcast messages expire (`stale_minutes`, 30 by default). So a demand
|
|
5
|
+
sent while no agent session was alive to receive it simply evaporated: nothing
|
|
6
|
+
to resume, because nothing had been written down. Closing the app looked like
|
|
7
|
+
the cause; the real cause was that the request was never recorded anywhere.
|
|
8
|
+
|
|
9
|
+
This is deliberately NOT a second memory. The vault remains the record: an
|
|
10
|
+
agent that takes a demand writes it there. This store exists for one narrow
|
|
11
|
+
reason — the cockpit is not an agent, carries no contract and has no
|
|
12
|
+
authenticated path to the vault, so it needs somewhere durable to leave a
|
|
13
|
+
request for the window in which nobody is listening. It holds the handoff, not
|
|
14
|
+
the history, and it should disappear the day the cockpit can record directly.
|
|
15
|
+
|
|
16
|
+
No TTL, on purpose: an unanswered demand is not stale, it is unanswered.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import uuid
|
|
22
|
+
|
|
23
|
+
from . import broadcast
|
|
24
|
+
from .storage import atomic_json, ensure_dir
|
|
25
|
+
|
|
26
|
+
SCHEMA = "crow-demands/1"
|
|
27
|
+
|
|
28
|
+
#: pending — written, nobody took it
|
|
29
|
+
#: taken — an agent is on it (with who and when)
|
|
30
|
+
#: done — finished, with a short note of the outcome
|
|
31
|
+
#: failed — attempted and abandoned, with the reason
|
|
32
|
+
STATES = ("pending", "taken", "done", "failed")
|
|
33
|
+
|
|
34
|
+
#: Keep finished demands around this long so the owner can still see what was
|
|
35
|
+
#: answered; they are history, not queue, and are pruned on write.
|
|
36
|
+
KEEP_DONE_HOURS = 72
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def path(home):
|
|
40
|
+
return broadcast.state_dir(home) / "demands.json"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _load(home):
|
|
44
|
+
data = broadcast.read_json(path(home)) or {}
|
|
45
|
+
if data.get("schema") != SCHEMA:
|
|
46
|
+
return {"schema": SCHEMA, "demands": []}
|
|
47
|
+
data.setdefault("demands", [])
|
|
48
|
+
return data
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _save(home, data):
|
|
52
|
+
target = path(home)
|
|
53
|
+
ensure_dir(target.parent)
|
|
54
|
+
atomic_json(target, data)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _age_hours(stamp):
|
|
58
|
+
import time
|
|
59
|
+
try:
|
|
60
|
+
return (time.time() - time.mktime(time.strptime((stamp or "")[:19], "%Y-%m-%dT%H:%M:%S"))) / 3600
|
|
61
|
+
except (ValueError, TypeError):
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def add(home, text, thread=None, source=None):
|
|
66
|
+
"""Record a demand. Called by whoever received it, before acting."""
|
|
67
|
+
text = (text or "").strip()
|
|
68
|
+
if not text:
|
|
69
|
+
raise ValueError("uma demanda vazia não é uma demanda")
|
|
70
|
+
record = {
|
|
71
|
+
"id": uuid.uuid4().hex[:10],
|
|
72
|
+
"text": text,
|
|
73
|
+
"state": "pending",
|
|
74
|
+
"createdAt": broadcast.now_iso(),
|
|
75
|
+
"thread": thread or None,
|
|
76
|
+
"source": source or "cockpit",
|
|
77
|
+
"takenBy": None,
|
|
78
|
+
"takenAt": None,
|
|
79
|
+
"closedAt": None,
|
|
80
|
+
"note": None,
|
|
81
|
+
"attempts": 0,
|
|
82
|
+
}
|
|
83
|
+
data = _load(home)
|
|
84
|
+
data["demands"].append(record)
|
|
85
|
+
_prune(data)
|
|
86
|
+
_save(home, data)
|
|
87
|
+
return record
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _find(data, demand_id):
|
|
91
|
+
return next((d for d in data["demands"] if d.get("id") == demand_id), None)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def take(home, demand_id, by=None):
|
|
95
|
+
"""Claim a demand. Refuses one already in hand, so two agents do not both
|
|
96
|
+
start the same work — the duplication this queue exists to prevent."""
|
|
97
|
+
data = _load(home)
|
|
98
|
+
record = _find(data, demand_id)
|
|
99
|
+
if not record:
|
|
100
|
+
raise ValueError("demanda desconhecida: " + str(demand_id))
|
|
101
|
+
if record["state"] == "taken" and record.get("takenBy") not in (None, by):
|
|
102
|
+
raise RuntimeError("a demanda " + demand_id + " já está com " + str(record.get("takenBy")))
|
|
103
|
+
if record["state"] in ("done", "failed"):
|
|
104
|
+
raise RuntimeError("a demanda " + demand_id + " já foi encerrada como " + record["state"])
|
|
105
|
+
record.update(state="taken", takenBy=by, takenAt=broadcast.now_iso(),
|
|
106
|
+
attempts=int(record.get("attempts") or 0) + 1)
|
|
107
|
+
_save(home, data)
|
|
108
|
+
return record
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def close(home, demand_id, state, note=None):
|
|
112
|
+
"""Finish a demand. `note` is what the owner needs to read, not a status."""
|
|
113
|
+
if state not in ("done", "failed"):
|
|
114
|
+
raise ValueError("estado final inválido: " + str(state))
|
|
115
|
+
if state == "failed" and not (note or "").strip():
|
|
116
|
+
raise ValueError("uma demanda que falhou precisa dizer por quê")
|
|
117
|
+
data = _load(home)
|
|
118
|
+
record = _find(data, demand_id)
|
|
119
|
+
if not record:
|
|
120
|
+
raise ValueError("demanda desconhecida: " + str(demand_id))
|
|
121
|
+
record.update(state=state, closedAt=broadcast.now_iso(), note=(note or "").strip() or None)
|
|
122
|
+
_prune(data)
|
|
123
|
+
_save(home, data)
|
|
124
|
+
return record
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def release(home, demand_id):
|
|
128
|
+
"""Put a taken demand back. An agent that cannot finish must hand it back
|
|
129
|
+
rather than leave it held by a session that is about to die."""
|
|
130
|
+
data = _load(home)
|
|
131
|
+
record = _find(data, demand_id)
|
|
132
|
+
if not record:
|
|
133
|
+
raise ValueError("demanda desconhecida: " + str(demand_id))
|
|
134
|
+
if record["state"] != "taken":
|
|
135
|
+
raise RuntimeError("a demanda " + demand_id + " não está tomada")
|
|
136
|
+
record.update(state="pending", takenBy=None, takenAt=None)
|
|
137
|
+
_save(home, data)
|
|
138
|
+
return record
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _prune(data):
|
|
142
|
+
kept = []
|
|
143
|
+
for record in data["demands"]:
|
|
144
|
+
if record.get("state") in ("done", "failed"):
|
|
145
|
+
age = _age_hours(record.get("closedAt"))
|
|
146
|
+
if age is not None and age > KEEP_DONE_HOURS:
|
|
147
|
+
continue
|
|
148
|
+
kept.append(record)
|
|
149
|
+
data["demands"] = kept
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def listing(home, pending_only=False, live_keys=None):
|
|
153
|
+
"""The queue, with abandonment made visible.
|
|
154
|
+
|
|
155
|
+
A demand held by a session that no longer exists is reported as `orphaned`
|
|
156
|
+
rather than `taken`: it is the one state that looks like progress and is
|
|
157
|
+
not, and it is exactly what happens when the owner's agent dies mid-task.
|
|
158
|
+
"""
|
|
159
|
+
data = _load(home)
|
|
160
|
+
rows = []
|
|
161
|
+
changed = False
|
|
162
|
+
for record in data["demands"]:
|
|
163
|
+
row = dict(record)
|
|
164
|
+
if record.get("state") == "taken" and live_keys is not None:
|
|
165
|
+
if record.get("takenBy") and record["takenBy"] not in live_keys:
|
|
166
|
+
row["orphaned"] = True
|
|
167
|
+
# Hand it back so somebody can pick it up again.
|
|
168
|
+
record.update(state="pending", takenBy=None, takenAt=None)
|
|
169
|
+
row["state"] = "pending"
|
|
170
|
+
changed = True
|
|
171
|
+
if pending_only and row.get("state") != "pending":
|
|
172
|
+
continue
|
|
173
|
+
rows.append(row)
|
|
174
|
+
if changed:
|
|
175
|
+
_save(home, data)
|
|
176
|
+
return rows
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def render(rows):
|
|
180
|
+
if not rows:
|
|
181
|
+
return "(nenhuma demanda registrada)"
|
|
182
|
+
mark = {"pending": "○", "taken": "●", "done": "✓", "failed": "✗"}
|
|
183
|
+
lines = []
|
|
184
|
+
for d in rows:
|
|
185
|
+
text = broadcast.clean(d.get("text"), 90) if hasattr(broadcast, "clean") else str(d.get("text"))[:90]
|
|
186
|
+
extra = []
|
|
187
|
+
if d.get("orphaned"):
|
|
188
|
+
extra.append("estava com uma sessão que morreu, devolvida à fila")
|
|
189
|
+
if d.get("takenBy"):
|
|
190
|
+
extra.append("com " + str(d["takenBy"])[:10])
|
|
191
|
+
if d.get("note"):
|
|
192
|
+
extra.append(str(d["note"])[:60])
|
|
193
|
+
lines.append(" " + mark.get(d.get("state"), "·") + " [" + str(d.get("id")) + "] " + text +
|
|
194
|
+
(" · " + "; ".join(extra) if extra else ""))
|
|
195
|
+
return "\n".join(lines)
|
package/package.json
CHANGED
package/release.json
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"crow_skills/__init__.py": {
|
|
29
29
|
"executable": false,
|
|
30
30
|
"kind": "file",
|
|
31
|
-
"sha256": "
|
|
31
|
+
"sha256": "3b0211c6914097b573cea26e30b34fef698b83056d512dc3f2f1de2e0c51ade5"
|
|
32
32
|
},
|
|
33
33
|
"crow_skills/__main__.py": {
|
|
34
34
|
"executable": false,
|
|
@@ -58,7 +58,12 @@
|
|
|
58
58
|
"crow_skills/cli.py": {
|
|
59
59
|
"executable": false,
|
|
60
60
|
"kind": "file",
|
|
61
|
-
"sha256": "
|
|
61
|
+
"sha256": "506a45896915a0effa409a6763c95ddabf46953aa68e16f958af1d2088e7ad18"
|
|
62
|
+
},
|
|
63
|
+
"crow_skills/demands.py": {
|
|
64
|
+
"executable": false,
|
|
65
|
+
"kind": "file",
|
|
66
|
+
"sha256": "ce2436f20878b92eb175143d6b36f03ed62d490315bbb01958aa1646ae9d8225"
|
|
62
67
|
},
|
|
63
68
|
"crow_skills/events.py": {
|
|
64
69
|
"executable": false,
|
|
@@ -148,7 +153,7 @@
|
|
|
148
153
|
"package.json": {
|
|
149
154
|
"executable": false,
|
|
150
155
|
"kind": "file",
|
|
151
|
-
"sha256": "
|
|
156
|
+
"sha256": "de3052ab0111c37ac40038dc78a1372e3c5696e87007a36fcc8241880bba9b14"
|
|
152
157
|
},
|
|
153
158
|
"skills/crow-accessibility/SKILL.md": {
|
|
154
159
|
"executable": false,
|
|
@@ -213,7 +218,7 @@
|
|
|
213
218
|
"skills/crow-behaviour/SKILL.md": {
|
|
214
219
|
"executable": false,
|
|
215
220
|
"kind": "file",
|
|
216
|
-
"sha256": "
|
|
221
|
+
"sha256": "0d943e32841e808c9f3e60f2faa97bac08b1ce143ec56099354f2bb4939d82f4"
|
|
217
222
|
},
|
|
218
223
|
"skills/crow-behaviour/agents/openai.yaml": {
|
|
219
224
|
"executable": false,
|
|
@@ -971,7 +976,7 @@
|
|
|
971
976
|
"sha256": "7dd3660097c3afeadb58357939b51da518a4da7cf2d7e684c2618b22f6c4a87b"
|
|
972
977
|
}
|
|
973
978
|
},
|
|
974
|
-
"revision": "
|
|
979
|
+
"revision": "1eef9e88688b6baa3a085c2d803127c983573b98",
|
|
975
980
|
"schema_version": 1,
|
|
976
|
-
"version": "0.
|
|
981
|
+
"version": "0.39.0"
|
|
977
982
|
}
|
|
@@ -62,6 +62,8 @@ Quando `crow-model-level` estiver instalada, o nível de cada unidade delegada
|
|
|
62
62
|
- **O braçal é delegado por padrão, não por escolha sua.** Uma unidade é braçal quando você consegue especificar o resultado e aceitá-lo pela evidência, sem ver o caminho: a edição que você já decidiu, a varredura com critério fechado, a reescrita mecânica, a coleta de evidência, rodar um comando e relatar o que saiu, levantar como uma coisa funciona hoje. Se é preciso decidir no meio do caminho, não é braçal — é desenho, e é seu. Executar você mesmo uma unidade braçal porque "o contexto já está carregado" é a falha característica desta skill: troca o seu contexto, que é o recurso escasso e não se recupera, por trabalho que qualquer executor faria, e o briefing que você economizou custa menos do que o contexto que você gastou.
|
|
63
63
|
- Ao delegar execução, descubra quem é o micro-agent em vez de presumir: `crow_micro.py probe` — com `codex` disponível e autenticado, os micro-agents são do codex; sem ele, são os do próprio CLI. Ao micro-agent vai só o braçal já especificado; mapear, planejar, desenhar e verificar no fim continuam seus. Leia [micro-agents](references/micro-agents.md).
|
|
64
64
|
- **Micro-agent ou bot autônomo: decida pela natureza da demanda, não por hábito.** Resultado que você especifica e aceita pela evidência, dentro do turno, é **micro-agent** — efêmero, você verifica e descarta. Demanda que persiste além do turno, dispara por evento e precisa de estado próprio entre execuções é **bot autônomo** — um processo que sobrevive à sessão, com memória externa própria e gatilho; ele não lembra, relê o que gravou e reconstrói o contexto a cada disparo. Micro-agent é o default; bot só quando a persistência ou o gatilho de evento é a essência da demanda, não um "seria bom ter". Bot e micro-agent usam igualmente o crow-broadcast e a mesma fronteira de autonomia desta skill; o criador continua responsável por ambos, verifica o resultado e age quando qualquer um fica ocioso.
|
|
65
|
+
- **Antes de pegar trabalho novo, veja o que o dono já pediu e ninguém assumiu.** Demanda digitada no cockpit fica registrada numa fila local sem validade (`crow-skills demand list --pending`), porque mensagem de broadcast expira e pedido sem resposta não é velho, é pendente. Ao iniciar, e sempre que ficar ocioso, leia a fila: se houver demanda compatível com o seu escopo autorizado, **assuma** (`demand take <id>`), execute, e encerre dizendo o desfecho (`demand done <id> "<o que saiu>"` ou `demand fail <id> "<por quê>"`). Se não puder terminar, **devolva** (`demand release <id>`) em vez de morrer segurando — a fila marca como órfã a demanda presa a uma sessão que sumiu, mas devolver na hora é honesto e evita a espera. A fila é entrega, não memória: ao assumir, registre o pedido na memória do dono, que é onde ele passa a existir de verdade.
|
|
66
|
+
|
|
65
67
|
- **O conhecimento do bot é seu para guardar, não dele.** A memória do bot é apartada e perecível: ela morre com a sessão e envelhece quando ele fica ocioso. O que ele sabe veio de você, no briefing — ele é consumidor do conhecimento, nunca o guardião. Então, antes de criar um bot de um papel que já existiu, **leia o que já foi acumulado** (`crow-skills bot knowledge --role-of <papel>`) e inclua no briefing o que valer, em vez de ensinar de novo o que já foi pago. Quando o bot reporta, **registre o que ele devolveu** (`crow-skills bot learned <id> "<o que apurou>"`): o retorno não registrado é conhecimento que ninguém mais tem. Esse registro é local e mecânico; **promover ao durável é seu**, pela memória do dono, porque o bot não toca o vault — só você toca. Destile, não acumule: nota que cresce a cada briefing recria, em outro lugar, o custo de contexto que a delegação existe para cortar. E trate o retorno como alegação a julgar antes de virar registro, não como verdade.
|
|
66
68
|
- Leia [adaptação ao runtime](references/orchestration.md) ao escolher workflow, subagentes ou execução sequencial. Não presuma nomes de ferramentas, paralelismo ou suporte a isolamento. Se o host exigir opt-in explícito do usuário para orquestração programável, esta skill não é esse opt-in: peça o opt-in da ferramenta, nunca permissão para aplicar esta skill, e sem ele siga com subagentes ou sequencialmente.
|
|
67
69
|
|