lynkr 1.0.1 → 2.0.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/CITATIONS.bib ADDED
@@ -0,0 +1,6 @@
1
+ @article{zhang2024ace,
2
+ title={Agentic Context Engineering},
3
+ author={Zhang et al.},
4
+ journal={arXiv:2510.04618},
5
+ year={2024}
6
+ }
package/README.md CHANGED
@@ -44,7 +44,8 @@
44
44
  10. [Troubleshooting](#troubleshooting)
45
45
  11. [Roadmap & Known Gaps](#roadmap--known-gaps)
46
46
  12. [FAQ](#faq)
47
- 13. [License](#license)
47
+ 13. [References](#references)
48
+ 14. [License](#license)
48
49
 
49
50
  ---
50
51
 
@@ -1345,6 +1346,7 @@ Replace `<workspace>` and `<endpoint-name>` with your Databricks workspace host
1345
1346
  - **503 Service Unavailable errors during normal load** – Check load shedding thresholds (`LOAD_SHEDDING_*`). Lower values may trigger too aggressively. Check `/metrics/observability` for memory usage patterns.
1346
1347
  - **Circuit breaker stuck in OPEN state** – Check `/metrics/circuit-breakers` to see failure counts. Verify backend service (Databricks/Azure) is accessible. Circuit will automatically attempt recovery after `CIRCUIT_BREAKER_TIMEOUT` (default: 60s).
1347
1348
  - **"Circuit breaker is OPEN" errors** – The circuit breaker detected too many failures and is protecting against cascading failures. Wait for timeout or fix the underlying issue. Check logs for root cause of failures.
1349
+ - **Azure OpenAI specific**: If using Azure OpenAI and seeing circuit breaker errors, verify your `AZURE_OPENAI_ENDPOINT` includes the full path (including `/openai/deployments/YOUR-DEPLOYMENT/chat/completions`). Missing endpoint variable or undefined returns can trigger circuit breaker protection.
1348
1350
  - **High latency after adding production features** – This is unexpected; middleware adds only ~7μs overhead. Check `/metrics/prometheus` for actual latency distribution. Verify network latency to backend services.
1349
1351
  - **Health check endpoint returns 503 but service seems healthy** – Check individual health check components in the response JSON. Database connectivity or memory issues may trigger this. Review logs for specific health check failures.
1350
1352
  - **Metrics endpoint shows incorrect data** – Metrics are in-memory and reset on restart. For persistent metrics, configure Prometheus scraping. Check that `METRICS_ENABLED=true`.
@@ -1538,6 +1540,16 @@ The graceful shutdown and health check endpoints ensure zero-downtime deployment
1538
1540
 
1539
1541
  ---
1540
1542
 
1543
+ ## References
1544
+
1545
+ Lynkr's design also includes ACE Framework informed by research in agentic AI systems and context engineering:
1546
+
1547
+ - **Zhang et al. (2024)**. *Agentic Context Engineering*. arXiv:2510.04618. [arXiv](https://arxiv.org/abs/2510.04618)
1548
+
1549
+ For BibTeX citations, see [CITATIONS.bib](CITATIONS.bib).
1550
+
1551
+ ---
1552
+
1541
1553
  ## License
1542
1554
 
1543
1555
  MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,31 @@
1
+ #!/bin/bash
2
+
3
+ # Monitor agent activity in real-time
4
+
5
+ echo "🔍 Monitoring Agent Activity"
6
+ echo "=============================="
7
+ echo ""
8
+
9
+ while true; do
10
+ clear
11
+ echo "🔍 Agent Statistics (refreshing every 3s)"
12
+ echo "=========================================="
13
+ echo ""
14
+
15
+ # Get stats
16
+ curl -s http://localhost:8080/v1/agents/stats | jq -r '.stats[] |
17
+ "Agent: \(.agent_type)
18
+ Executions: \(.total_executions) (\(.completed) completed, \(.failed) failed)
19
+ Avg Duration: \(.avg_duration_ms)ms
20
+ Tokens: \(.total_input_tokens) in / \(.total_output_tokens) out
21
+ "' || echo "Proxy not responding..."
22
+
23
+ echo ""
24
+ echo "Latest transcripts:"
25
+ ls -lt data/agent-transcripts/*.jsonl 2>/dev/null | head -3 || echo "No transcripts yet"
26
+
27
+ echo ""
28
+ echo "Press Ctrl+C to stop monitoring"
29
+
30
+ sleep 3
31
+ done
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lynkr",
3
- "version": "1.0.1",
4
- "description": "Self-hosted Claude Code proxy with Databricks and Azure Anthropic adapters, workspace tooling, and MCP integration.",
3
+ "version": "2.0.0",
4
+ "description": "Self-hosted Claude Code proxy with Databricks,Azure adapters, workspace tooling, and MCP integration.",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "lynkr": "./bin/cli.js",
@@ -41,6 +41,7 @@
41
41
  "node": ">=20.0.0"
42
42
  },
43
43
  "dependencies": {
44
+ "@azure/openai": "^2.0.0",
44
45
  "better-sqlite3": "^9.4.0",
45
46
  "compression": "^1.7.4",
46
47
  "diff": "^5.2.0",
@@ -48,6 +49,8 @@
48
49
  "express": "^5.1.0",
49
50
  "express-rate-limit": "^8.2.1",
50
51
  "fast-glob": "^3.3.2",
52
+ "js-yaml": "^4.1.1",
53
+ "openai": "^6.14.0",
51
54
  "pino": "^8.17.2",
52
55
  "pino-http": "^8.6.0",
53
56
  "tree-sitter": "^0.20.1",
@@ -0,0 +1,220 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const logger = require("../logger");
4
+
5
+ class ContextManager {
6
+ constructor() {
7
+ this.transcriptsDir = path.join(process.cwd(), "data", "agent-transcripts");
8
+ this.ensureTranscriptsDir();
9
+ }
10
+
11
+ ensureTranscriptsDir() {
12
+ if (!fs.existsSync(this.transcriptsDir)) {
13
+ fs.mkdirSync(this.transcriptsDir, { recursive: true });
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Create fresh context for subagent
19
+ * @param {Object} agentDef - Agent definition
20
+ * @param {string} taskPrompt - Task from main agent
21
+ * @param {Object} mainContext - Minimal context from main agent
22
+ * @returns {Object} - Fresh context for subagent
23
+ */
24
+ createSubagentContext(agentDef, taskPrompt, mainContext = {}) {
25
+ const agentId = this.generateAgentId();
26
+ const transcriptPath = path.join(this.transcriptsDir, `agent-${agentId}.jsonl`);
27
+
28
+ // Initialize transcript file
29
+ fs.writeFileSync(transcriptPath, "");
30
+
31
+ // Build minimal context (NOT full main agent history)
32
+ const messages = [];
33
+
34
+ // System prompt from agent definition
35
+ messages.push({
36
+ role: "system",
37
+ content: agentDef.systemPrompt
38
+ });
39
+
40
+ // Optional: Add minimal context from main agent
41
+ if (mainContext.relevant_context) {
42
+ messages.push({
43
+ role: "system",
44
+ content: `Context from main agent:\n${mainContext.relevant_context}`
45
+ });
46
+ }
47
+
48
+ // Task prompt
49
+ messages.push({
50
+ role: "user",
51
+ content: taskPrompt
52
+ });
53
+
54
+ const context = {
55
+ agentId,
56
+ agentName: agentDef.name,
57
+ transcriptPath,
58
+ messages,
59
+ steps: 0,
60
+ maxSteps: agentDef.maxSteps,
61
+ model: agentDef.model,
62
+ allowedTools: agentDef.allowedTools,
63
+ startTime: Date.now(),
64
+
65
+ // Token tracking
66
+ inputTokens: 0,
67
+ outputTokens: 0,
68
+
69
+ // State
70
+ terminated: false,
71
+ result: null
72
+ };
73
+
74
+ this.writeTranscriptEntry(transcriptPath, {
75
+ type: "agent_start",
76
+ agentId,
77
+ agentName: agentDef.name,
78
+ taskPrompt,
79
+ timestamp: Date.now()
80
+ });
81
+
82
+ logger.info({ agentId, agentName: agentDef.name }, "Created fresh subagent context");
83
+
84
+ return context;
85
+ }
86
+
87
+ /**
88
+ * Add message to subagent context
89
+ */
90
+ addMessage(context, message) {
91
+ context.messages.push(message);
92
+
93
+ this.writeTranscriptEntry(context.transcriptPath, {
94
+ type: "message",
95
+ agentId: context.agentId,
96
+ message,
97
+ timestamp: Date.now()
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Record tool execution in transcript
103
+ */
104
+ recordToolCall(context, toolName, input, output, error = null) {
105
+ this.writeTranscriptEntry(context.transcriptPath, {
106
+ type: "tool_call",
107
+ agentId: context.agentId,
108
+ step: context.steps,
109
+ toolName,
110
+ input,
111
+ output: error ? null : output,
112
+ error: error ? error.message : null,
113
+ timestamp: Date.now()
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Complete subagent execution
119
+ */
120
+ completeExecution(context, result) {
121
+ context.terminated = true;
122
+ context.result = result;
123
+
124
+ this.writeTranscriptEntry(context.transcriptPath, {
125
+ type: "agent_complete",
126
+ agentId: context.agentId,
127
+ result,
128
+ stats: {
129
+ steps: context.steps,
130
+ durationMs: Date.now() - context.startTime,
131
+ inputTokens: context.inputTokens,
132
+ outputTokens: context.outputTokens
133
+ },
134
+ timestamp: Date.now()
135
+ });
136
+
137
+ logger.info({
138
+ agentId: context.agentId,
139
+ steps: context.steps,
140
+ durationMs: Date.now() - context.startTime
141
+ }, "Subagent execution completed");
142
+ }
143
+
144
+ /**
145
+ * Fail subagent execution
146
+ */
147
+ failExecution(context, error) {
148
+ context.terminated = true;
149
+
150
+ this.writeTranscriptEntry(context.transcriptPath, {
151
+ type: "agent_failed",
152
+ agentId: context.agentId,
153
+ error: error.message,
154
+ stats: {
155
+ steps: context.steps,
156
+ durationMs: Date.now() - context.startTime
157
+ },
158
+ timestamp: Date.now()
159
+ });
160
+
161
+ logger.error({
162
+ agentId: context.agentId,
163
+ error: error.message
164
+ }, "Subagent execution failed");
165
+ }
166
+
167
+ /**
168
+ * Write entry to transcript file (JSONL format)
169
+ */
170
+ writeTranscriptEntry(transcriptPath, entry) {
171
+ try {
172
+ fs.appendFileSync(transcriptPath, JSON.stringify(entry) + "\n");
173
+ } catch (error) {
174
+ logger.warn({ error: error.message }, "Failed to write transcript entry");
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Read transcript for debugging
180
+ */
181
+ readTranscript(agentId) {
182
+ const transcriptPath = path.join(this.transcriptsDir, `agent-${agentId}.jsonl`);
183
+
184
+ if (!fs.existsSync(transcriptPath)) {
185
+ return null;
186
+ }
187
+
188
+ const lines = fs.readFileSync(transcriptPath, "utf8").split("\n").filter(l => l.trim());
189
+ return lines.map(line => JSON.parse(line));
190
+ }
191
+
192
+ /**
193
+ * Generate unique agent ID
194
+ */
195
+ generateAgentId() {
196
+ return `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
197
+ }
198
+
199
+ /**
200
+ * Clean old transcripts (older than 7 days)
201
+ */
202
+ cleanOldTranscripts() {
203
+ const maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days
204
+ const now = Date.now();
205
+
206
+ const files = fs.readdirSync(this.transcriptsDir);
207
+
208
+ for (const file of files) {
209
+ const filePath = path.join(this.transcriptsDir, file);
210
+ const stats = fs.statSync(filePath);
211
+
212
+ if (now - stats.mtimeMs > maxAge) {
213
+ fs.unlinkSync(filePath);
214
+ logger.debug({ file }, "Cleaned old transcript");
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ module.exports = ContextManager;