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 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 v6.83.0
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
- # With provider selection (supports .md and .json PRDs)
190
- ./autonomy/run.sh --provider claude ./prd.md # Default, full features
191
- ./autonomy/run.sh --provider codex ./prd.json # GPT-5.3 Codex, degraded mode
192
- ./autonomy/run.sh --provider gemini ./prd.md # Gemini 3 Pro, degraded mode
193
- ./autonomy/run.sh --provider cline ./prd.md # Cline CLI, degraded mode
194
- ./autonomy/run.sh --provider aider ./prd.md # Aider (18+ providers), degraded mode
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
- # Or via CLI wrapper
197
- loki start --provider codex ./prd.md
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
- ./autonomy/run.sh --parallel ./prd.md
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
- **v6.83.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
323
+ **v7.0.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 6.83.0
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
+ ]