loki-mode 6.83.0 → 7.0.1
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/SKILL.md +62 -11
- package/VERSION +1 -1
- package/agents/managed_registry.py +246 -0
- package/agents/types.json +330 -0
- package/autonomy/completion-council.sh +226 -0
- package/autonomy/loki +346 -15
- package/autonomy/run.sh +357 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +235 -0
- package/docs/INSTALLATION.md +1 -1
- package/mcp/__init__.py +1 -1
- package/mcp/managed_tools.py +234 -0
- package/mcp/server.py +22 -0
- package/memory/managed_memory/__init__.py +9 -0
- package/memory/managed_memory/retrieve.py +237 -1
- package/package.json +4 -2
- package/providers/managed.py +789 -0
- package/skills/00-index.md +1 -0
- package/skills/memory.md +185 -0
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode
|
|
6
|
+
# Loki Mode v7.0.1
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -181,23 +181,36 @@ GROWTH ──[continuous improvement loop]──> GROWTH
|
|
|
181
181
|
|
|
182
182
|
## Invocation
|
|
183
183
|
|
|
184
|
+
**Unified entry point (v6.84.0):** `loki start` auto-detects whether the input is a PRD file, an issue URL, or an issue number. No need to pick between `loki start` and `loki run` -- the single command handles all cases.
|
|
185
|
+
|
|
184
186
|
```bash
|
|
185
187
|
# Standard mode (Claude - full features)
|
|
186
188
|
claude --dangerously-skip-permissions
|
|
187
189
|
# Then say: "Loki Mode" or "Loki Mode with PRD at path/to/prd.md" (or .json)
|
|
188
190
|
|
|
189
|
-
#
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
# Unified `loki start` -- one command, auto-detected mode
|
|
192
|
+
loki start # no arg: analyze current dir, auto-generate PRD
|
|
193
|
+
loki start ./prd.md # PRD mode (.md/.json/.txt/.yaml)
|
|
194
|
+
loki start https://github.com/o/r/issues/42 # ISSUE mode (GitHub URL)
|
|
195
|
+
loki start 123 # ISSUE mode (GitHub issue in current repo)
|
|
196
|
+
loki start PROJ-456 # ISSUE mode (Jira)
|
|
197
|
+
loki start owner/repo#789 # ISSUE mode (GitHub specific repo)
|
|
198
|
+
loki start --prd ./prd.md # Explicit PRD mode (overrides detection)
|
|
199
|
+
loki start --issue 123 # Explicit issue mode (overrides detection)
|
|
195
200
|
|
|
196
|
-
#
|
|
197
|
-
loki start --provider
|
|
201
|
+
# With provider selection (supports .md and .json PRDs)
|
|
202
|
+
loki start --provider claude ./prd.md # Default, full features
|
|
203
|
+
loki start --provider codex ./prd.json # GPT-5.3 Codex, degraded mode
|
|
204
|
+
loki start --provider gemini ./prd.md # Gemini 3 Pro, degraded mode
|
|
205
|
+
loki start --provider cline ./prd.md # Cline CLI, degraded mode
|
|
206
|
+
loki start --provider aider ./prd.md # Aider (18+ providers), degraded mode
|
|
198
207
|
|
|
199
208
|
# Parallel mode (git worktrees, Claude only)
|
|
200
|
-
|
|
209
|
+
loki start ./prd.md --parallel
|
|
210
|
+
loki start 123 --ship # Issue -> PR -> auto-merge
|
|
211
|
+
|
|
212
|
+
# Legacy: `loki run <issue>` still works but prints a deprecation notice.
|
|
213
|
+
# It is an alias for `loki start <issue>` and will be removed in a future major.
|
|
201
214
|
```
|
|
202
215
|
|
|
203
216
|
**Provider capabilities:**
|
|
@@ -258,6 +271,44 @@ Auto-detected or force with `LOKI_COMPLEXITY`:
|
|
|
258
271
|
|
|
259
272
|
---
|
|
260
273
|
|
|
274
|
+
## Managed Agents Integration (v7.0.1)
|
|
275
|
+
|
|
276
|
+
Opt-in integration with Claude Managed Agents (released Apr 2026). Gives
|
|
277
|
+
Loki cross-project audited memory and real multiagent councils. Features
|
|
278
|
+
are BAKED INTO existing RARV-C and council flows -- no new commands to
|
|
279
|
+
learn.
|
|
280
|
+
|
|
281
|
+
**All flags default false.** Default behavior is identical to v7.0.1.
|
|
282
|
+
|
|
283
|
+
| Flag | Purpose | Status |
|
|
284
|
+
|------|---------|--------|
|
|
285
|
+
| `LOKI_MANAGED_AGENTS` | Parent gate; required for every managed path | stable |
|
|
286
|
+
| `LOKI_MANAGED_MEMORY` | REASON augment + REFLECT shadow-write from `.loki/memory/` to Managed Agents store | stable (tested with fakes) |
|
|
287
|
+
| `LOKI_MANAGED_MEMORY_HYDRATE` | Session-boot pull of semantic patterns + skills from store | stable (tested with fakes) |
|
|
288
|
+
| `LOKI_EXPERIMENTAL_MANAGED_AGENTS` | Umbrella for multiagent session path | RESEARCH PREVIEW |
|
|
289
|
+
| `LOKI_EXPERIMENTAL_MANAGED_REVIEW` | Managed code-review council via `callable_agents` | RESEARCH PREVIEW |
|
|
290
|
+
| `LOKI_EXPERIMENTAL_MANAGED_COUNCIL` | Managed completion council via `callable_agents` | RESEARCH PREVIEW |
|
|
291
|
+
|
|
292
|
+
Fail-fast: child-on + parent-off exits 2 with clear error. API
|
|
293
|
+
unreachable falls back to local path with a `managed_agents_fallback`
|
|
294
|
+
event to `.loki/managed/events.ndjson`. No retry storm.
|
|
295
|
+
|
|
296
|
+
**Flip-on order (recommended):**
|
|
297
|
+
1. `LOKI_MANAGED_AGENTS=true LOKI_MANAGED_MEMORY=true` (memory mirror).
|
|
298
|
+
2. Add `LOKI_MANAGED_MEMORY_HYDRATE=true` after one-week soak.
|
|
299
|
+
3. Keep `LOKI_EXPERIMENTAL_*` off until multiagent graduates from
|
|
300
|
+
research preview.
|
|
301
|
+
|
|
302
|
+
**NOT TESTED against live Anthropic API.** Automated CI uses
|
|
303
|
+
`memory/managed_memory/fakes.py`. Beta header pinned to
|
|
304
|
+
`managed-agents-2026-04-01`. If the SDK shape differs, calls raise
|
|
305
|
+
`AttributeError`/`TypeError` which are caught and translated to
|
|
306
|
+
`ManagedUnavailable` -> fallback to local path.
|
|
307
|
+
|
|
308
|
+
See `skills/memory.md` for the full integration guide.
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
261
312
|
## Planned Features
|
|
262
313
|
|
|
263
314
|
The following features are documented in skill modules but not yet fully automated:
|
|
@@ -269,4 +320,4 @@ The following features are documented in skill modules but not yet fully automat
|
|
|
269
320
|
| Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
|
|
270
321
|
| Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
|
|
271
322
|
|
|
272
|
-
**
|
|
323
|
+
**v7.0.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
7.0.1
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Loki Managed Agents - Agent materialization registry (Phase 2 foundation).
|
|
3
|
+
|
|
4
|
+
Lazy registry that maps Loki pool names (as declared in agents/types.json)
|
|
5
|
+
to Managed Agent IDs minted via `client.beta.agents.create(...)`. Created
|
|
6
|
+
IDs are cached on disk at .loki/managed/agent_ids.json so subsequent
|
|
7
|
+
iterations re-use the same agent.
|
|
8
|
+
|
|
9
|
+
Design:
|
|
10
|
+
- LAZY ONLY: no network calls at import time, no calls on Loki
|
|
11
|
+
startup. The first call to materialize_agent(pool_name) triggers
|
|
12
|
+
the single create-or-fetch round trip for that pool.
|
|
13
|
+
- Uses the shared managed-agents client from providers.managed so
|
|
14
|
+
the anthropic SDK stays imported from the two allowed files only.
|
|
15
|
+
- agents/types.json is the single source of truth for pool names,
|
|
16
|
+
personas, and capabilities. This registry treats the pool entry
|
|
17
|
+
as opaque and forwards the persona as the agent's system prompt.
|
|
18
|
+
- Writes to .loki/managed/agent_ids.json are atomic (write-to-temp
|
|
19
|
+
then rename) so a crash mid-write does not corrupt the cache.
|
|
20
|
+
|
|
21
|
+
Not wired into autonomy/run.sh directly. Consumers go through
|
|
22
|
+
providers.managed.resolve_agent_ids(pool_names).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
import tempfile
|
|
30
|
+
import threading
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Any, Dict, List, Optional
|
|
33
|
+
|
|
34
|
+
# Single import site for the event emitter -- shared with memory/managed_memory.
|
|
35
|
+
from memory.managed_memory.events import emit_managed_event
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
_CACHE_FILE_REL = ".loki/managed/agent_ids.json"
|
|
39
|
+
_TYPES_FILE_REL = "agents/types.json"
|
|
40
|
+
|
|
41
|
+
_cache_lock = threading.Lock()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# Paths
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _target_dir() -> Path:
|
|
50
|
+
base = os.environ.get("LOKI_TARGET_DIR") or os.getcwd()
|
|
51
|
+
return Path(base)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _cache_path() -> Path:
|
|
55
|
+
return _target_dir() / _CACHE_FILE_REL
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _types_path() -> Path:
|
|
59
|
+
# types.json lives next to this module in the repo layout. The repo
|
|
60
|
+
# root is the parent of agents/ for the normal install.
|
|
61
|
+
here = Path(__file__).resolve().parent
|
|
62
|
+
local = here / "types.json"
|
|
63
|
+
if local.exists():
|
|
64
|
+
return local
|
|
65
|
+
# Fallback to target_dir for relocated installs.
|
|
66
|
+
return _target_dir() / _TYPES_FILE_REL
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
# Cache I/O
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _load_cache() -> Dict[str, str]:
|
|
75
|
+
"""Load pool_name -> agent_id mapping from disk. {} on any error."""
|
|
76
|
+
p = _cache_path()
|
|
77
|
+
if not p.exists():
|
|
78
|
+
return {}
|
|
79
|
+
try:
|
|
80
|
+
with open(p, "r", encoding="utf-8") as f:
|
|
81
|
+
data = json.load(f)
|
|
82
|
+
except (OSError, json.JSONDecodeError):
|
|
83
|
+
return {}
|
|
84
|
+
if not isinstance(data, dict):
|
|
85
|
+
return {}
|
|
86
|
+
return {k: v for k, v in data.items() if isinstance(k, str) and isinstance(v, str)}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _save_cache(cache: Dict[str, str]) -> None:
|
|
90
|
+
"""Atomic write of the cache dict to disk."""
|
|
91
|
+
p = _cache_path()
|
|
92
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
93
|
+
# Use a named temp file in the same directory so os.replace is atomic.
|
|
94
|
+
tmp_fd, tmp_name = tempfile.mkstemp(
|
|
95
|
+
prefix=".agent_ids-", suffix=".json.tmp", dir=str(p.parent)
|
|
96
|
+
)
|
|
97
|
+
try:
|
|
98
|
+
with os.fdopen(tmp_fd, "w", encoding="utf-8") as f:
|
|
99
|
+
json.dump(cache, f, indent=2, sort_keys=True)
|
|
100
|
+
os.replace(tmp_name, p)
|
|
101
|
+
except OSError as e:
|
|
102
|
+
# Best-effort cleanup; swallow the error so the caller can
|
|
103
|
+
# proceed -- a stale cache is recoverable, a raise here would
|
|
104
|
+
# break the RARV loop.
|
|
105
|
+
try:
|
|
106
|
+
os.unlink(tmp_name)
|
|
107
|
+
except OSError:
|
|
108
|
+
pass
|
|
109
|
+
emit_managed_event(
|
|
110
|
+
"managed_agents_fallback",
|
|
111
|
+
{
|
|
112
|
+
"op": "registry_save_cache",
|
|
113
|
+
"reason": "cache_write_failed",
|
|
114
|
+
"detail": str(e),
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
# types.json lookup
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _load_types() -> List[Dict[str, Any]]:
|
|
125
|
+
try:
|
|
126
|
+
with open(_types_path(), "r", encoding="utf-8") as f:
|
|
127
|
+
data = json.load(f)
|
|
128
|
+
except (OSError, json.JSONDecodeError):
|
|
129
|
+
return []
|
|
130
|
+
if not isinstance(data, list):
|
|
131
|
+
return []
|
|
132
|
+
return [d for d in data if isinstance(d, dict)]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _lookup_type(pool_name: str) -> Optional[Dict[str, Any]]:
|
|
136
|
+
for entry in _load_types():
|
|
137
|
+
if entry.get("type") == pool_name or entry.get("name") == pool_name:
|
|
138
|
+
return entry
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _persona_for(pool_name: str) -> str:
|
|
143
|
+
entry = _lookup_type(pool_name) or {}
|
|
144
|
+
persona = entry.get("persona") or ""
|
|
145
|
+
cap = entry.get("capabilities") or ""
|
|
146
|
+
if persona and cap:
|
|
147
|
+
return f"{persona}\n\nCapabilities: {cap}"
|
|
148
|
+
return persona or f"Loki pool agent: {pool_name}"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _display_name_for(pool_name: str) -> str:
|
|
152
|
+
entry = _lookup_type(pool_name) or {}
|
|
153
|
+
return entry.get("name") or pool_name
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# ---------------------------------------------------------------------------
|
|
157
|
+
# Materialization via the anthropic SDK
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _call_create_agent(pool_name: str) -> str:
|
|
162
|
+
"""
|
|
163
|
+
Invoke the anthropic beta agents.create endpoint for `pool_name`.
|
|
164
|
+
|
|
165
|
+
Raises RuntimeError on any SDK shape / network / auth error. Callers
|
|
166
|
+
translate to a fallback-capable flow.
|
|
167
|
+
"""
|
|
168
|
+
# Deferred import to keep the module SDK-free at import time.
|
|
169
|
+
from providers.managed import _get_client # local import of shared client
|
|
170
|
+
|
|
171
|
+
client = _get_client()
|
|
172
|
+
beta = getattr(client, "beta", None)
|
|
173
|
+
if beta is None:
|
|
174
|
+
raise RuntimeError("anthropic SDK missing `beta` namespace")
|
|
175
|
+
|
|
176
|
+
agents_ns = getattr(beta, "agents", None)
|
|
177
|
+
if agents_ns is None or not hasattr(agents_ns, "create"):
|
|
178
|
+
raise RuntimeError("anthropic.beta.agents.create not available in SDK")
|
|
179
|
+
|
|
180
|
+
persona = _persona_for(pool_name)
|
|
181
|
+
display_name = _display_name_for(pool_name)
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
created = agents_ns.create(
|
|
185
|
+
name=display_name,
|
|
186
|
+
system=persona,
|
|
187
|
+
metadata={"loki_pool": pool_name},
|
|
188
|
+
)
|
|
189
|
+
except (AttributeError, TypeError) as e:
|
|
190
|
+
raise RuntimeError(f"agents.create shape error: {e}")
|
|
191
|
+
except Exception as e:
|
|
192
|
+
raise RuntimeError(f"agents.create failed for {pool_name!r}: {e}")
|
|
193
|
+
|
|
194
|
+
agent_id = (
|
|
195
|
+
getattr(created, "id", None)
|
|
196
|
+
or (created.get("id") if isinstance(created, dict) else None)
|
|
197
|
+
or getattr(created, "agent_id", None)
|
|
198
|
+
)
|
|
199
|
+
if not agent_id:
|
|
200
|
+
raise RuntimeError(
|
|
201
|
+
f"agents.create returned no id for {pool_name!r}: {created!r}"
|
|
202
|
+
)
|
|
203
|
+
return str(agent_id)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# ---------------------------------------------------------------------------
|
|
207
|
+
# Public API
|
|
208
|
+
# ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def materialize_agent(pool_name: str) -> str:
|
|
212
|
+
"""
|
|
213
|
+
Return a Managed Agent ID for the given pool name.
|
|
214
|
+
|
|
215
|
+
Cache lookup first. On miss, call `client.beta.agents.create(...)`
|
|
216
|
+
with the persona from agents/types.json and persist the resulting
|
|
217
|
+
ID to .loki/managed/agent_ids.json.
|
|
218
|
+
|
|
219
|
+
Raises RuntimeError on failure (no silent fallback; the caller in
|
|
220
|
+
providers.managed.resolve_agent_ids translates into ManagedUnavailable).
|
|
221
|
+
"""
|
|
222
|
+
if not pool_name or not isinstance(pool_name, str):
|
|
223
|
+
raise RuntimeError(f"invalid pool_name: {pool_name!r}")
|
|
224
|
+
|
|
225
|
+
with _cache_lock:
|
|
226
|
+
cache = _load_cache()
|
|
227
|
+
cached = cache.get(pool_name)
|
|
228
|
+
if cached:
|
|
229
|
+
return cached
|
|
230
|
+
|
|
231
|
+
# Create it. Anything that goes wrong raises.
|
|
232
|
+
agent_id = _call_create_agent(pool_name)
|
|
233
|
+
cache[pool_name] = agent_id
|
|
234
|
+
_save_cache(cache)
|
|
235
|
+
emit_managed_event(
|
|
236
|
+
"managed_agent_materialized",
|
|
237
|
+
{"op": "materialize_agent", "pool_name": pool_name, "agent_id": agent_id},
|
|
238
|
+
)
|
|
239
|
+
return agent_id
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
__all__ = [
|
|
243
|
+
"materialize_agent",
|
|
244
|
+
"_load_cache",
|
|
245
|
+
"_save_cache",
|
|
246
|
+
]
|