Skip to content

Commit b4511a4

Browse files
RoyLinRoyLin
authored andcommitted
refactor: remove middleware and framework code, add agents_md examples
- Remove experimental middleware system from core and SDKs - Remove a3s_code_framework (unused Python DI framework) - Add agents_md_demo examples for both Node.js and Python - Clean up unused demo files
1 parent 250e13c commit b4511a4

24 files changed

Lines changed: 365 additions & 1812 deletions

core/examples/ahp_server.ts

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#!/usr/bin/env node
2+
/**
3+
* TypeScript AHP (Agent Harness Protocol) reference server
4+
*
5+
* This server demonstrates:
6+
* - Depth-aware policy enforcement (stricter rules for sub-agents)
7+
* - Pattern-based command blocking
8+
* - Sensitive output detection
9+
* - JSON-RPC 2.0 over stdio transport
10+
*
11+
* Usage:
12+
* npx ts-node ahp_server.ts
13+
*
14+
* Attach to A3S Code session:
15+
* import { Agent, HarnessServer } from '@a3s-lab/code';
16+
* const session = agent.session('.', {
17+
* harnessServer: new HarnessServer('npx', ['ts-node', 'ahp_server.ts'])
18+
* });
19+
*/
20+
21+
import * as readline from 'readline';
22+
23+
interface AhpMessage {
24+
jsonrpc: string;
25+
id?: number;
26+
method: string;
27+
params: {
28+
event_type: string;
29+
payload: any;
30+
meta: { depth: number };
31+
};
32+
}
33+
34+
interface AhpResponse {
35+
action: 'continue' | 'block' | 'skip' | 'retry';
36+
reason?: string;
37+
modified?: any;
38+
retry_delay_ms?: number;
39+
}
40+
41+
// Dangerous command patterns (depth-aware blocking)
42+
const BLOCKED_PATTERNS = [
43+
/rm\s+-rf\s+\//, // rm -rf /
44+
/:\(\)\{.*\};\s*:/, // fork bomb
45+
/curl.*\|\s*bash/, // curl | bash
46+
/wget.*\|\s*sh/, // wget | sh
47+
/dd\s+if=.*of=\/dev/, // dd to block device
48+
];
49+
50+
// Sensitive data patterns (for post-tool detection)
51+
const SENSITIVE_OUTPUT = [
52+
/sk-[a-zA-Z0-9]{48}/, // OpenAI API key
53+
/sk-ant-[a-zA-Z0-9-]{95}/, // Anthropic API key
54+
/-----BEGIN.*PRIVATE KEY-----/, // Private keys
55+
/ghp_[a-zA-Z0-9]{36}/, // GitHub personal access token
56+
];
57+
58+
function isDangerous(command: string): boolean {
59+
return BLOCKED_PATTERNS.some(pattern => pattern.test(command));
60+
}
61+
62+
function hasSensitiveOutput(output: string): boolean {
63+
return SENSITIVE_OUTPUT.some(pattern => pattern.test(output));
64+
}
65+
66+
function handlePreToolUse(payload: any, depth: number): AhpResponse {
67+
const tool = payload.tool || '';
68+
const command = payload.args?.command || '';
69+
70+
// Depth-aware: stricter policy for sub-agents
71+
if (tool === 'Bash') {
72+
if (isDangerous(command)) {
73+
const reason = depth > 0
74+
? `Blocked dangerous command at depth ${depth}: ${command.slice(0, 50)}`
75+
: `Blocked dangerous command: ${command.slice(0, 50)}`;
76+
console.error(`[BLOCK] ${reason}`);
77+
return { action: 'block', reason };
78+
}
79+
80+
// Block network access for depth > 1
81+
if (depth > 1 && /curl|wget|nc|telnet|ssh/.test(command)) {
82+
console.error(`[BLOCK] Network access blocked at depth ${depth}`);
83+
return { action: 'block', reason: 'Network access not allowed for nested agents' };
84+
}
85+
86+
// Block file system modifications for depth > 2
87+
if (depth > 2 && /rm|mv|cp|chmod|chown/.test(command)) {
88+
console.error(`[BLOCK] File system modification blocked at depth ${depth}`);
89+
return { action: 'block', reason: 'File modifications not allowed at this depth' };
90+
}
91+
}
92+
93+
return { action: 'continue' };
94+
}
95+
96+
function handlePrePrompt(payload: any, depth: number): AhpResponse {
97+
// Could inject additional context or modify the prompt here
98+
console.error(`[INFO] Pre-prompt at depth ${depth}`);
99+
return { action: 'continue' };
100+
}
101+
102+
function handlePostToolUse(payload: any, depth: number): void {
103+
const tool = payload.tool || '';
104+
const output = payload.output || '';
105+
106+
if (hasSensitiveOutput(output)) {
107+
console.error(`[ALERT] Sensitive data detected in ${tool} output at depth ${depth}`);
108+
// In production: send to audit log, trigger alert, redact output, etc.
109+
}
110+
}
111+
112+
function handleNotification(eventType: string, payload: any, depth: number): void {
113+
switch (eventType) {
114+
case 'post_tool_use':
115+
handlePostToolUse(payload, depth);
116+
break;
117+
case 'session_start':
118+
console.error(`[INFO] Session started: ${payload.session_id} (depth ${depth})`);
119+
break;
120+
case 'session_end':
121+
console.error(`[INFO] Session ended: ${payload.session_id} (depth ${depth})`);
122+
break;
123+
case 'generate_start':
124+
console.error(`[INFO] LLM generation started (depth ${depth})`);
125+
break;
126+
case 'generate_end':
127+
console.error(`[INFO] LLM generation ended (depth ${depth})`);
128+
break;
129+
case 'on_error':
130+
console.error(`[ERROR] Error in session: ${payload.error} (depth ${depth})`);
131+
break;
132+
default:
133+
// Ignore other notifications
134+
break;
135+
}
136+
}
137+
138+
function handleRequest(eventType: string, payload: any, depth: number): AhpResponse {
139+
switch (eventType) {
140+
case 'pre_tool_use':
141+
return handlePreToolUse(payload, depth);
142+
case 'pre_prompt':
143+
return handlePrePrompt(payload, depth);
144+
default:
145+
return { action: 'continue' };
146+
}
147+
}
148+
149+
// Main loop: read newline-delimited JSON from stdin
150+
const rl = readline.createInterface({
151+
input: process.stdin,
152+
output: process.stdout,
153+
terminal: false,
154+
});
155+
156+
console.error('[INFO] TypeScript AHP harness server started');
157+
console.error('[INFO] Listening for JSON-RPC 2.0 messages on stdin...');
158+
159+
rl.on('line', (line: string) => {
160+
try {
161+
const msg: AhpMessage = JSON.parse(line);
162+
const { event_type, payload, meta } = msg.params;
163+
const depth = meta?.depth ?? 0;
164+
const reqId = msg.id;
165+
166+
if (reqId === undefined) {
167+
// Notification (fire-and-forget)
168+
handleNotification(event_type, payload, depth);
169+
} else {
170+
// Request (blocking)
171+
const result = handleRequest(event_type, payload, depth);
172+
const response = {
173+
jsonrpc: '2.0',
174+
id: reqId,
175+
result,
176+
};
177+
console.log(JSON.stringify(response));
178+
}
179+
} catch (err) {
180+
console.error(`[ERROR] Failed to parse message: ${err}`);
181+
}
182+
});
183+
184+
rl.on('close', () => {
185+
console.error('[INFO] Harness server shutting down');
186+
process.exit(0);
187+
});

core/src/agent_api.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,36 @@ impl Agent {
997997
.clone()
998998
.unwrap_or_else(|| self.config.prompt_slots.clone());
999999

1000+
// Auto-load AGENTS.md from workspace root (similar to Claude Code's CLAUDE.md)
1001+
let agents_md_path = canonical.join("AGENTS.md");
1002+
if agents_md_path.exists() && agents_md_path.is_file() {
1003+
match std::fs::read_to_string(&agents_md_path) {
1004+
Ok(content) if !content.trim().is_empty() => {
1005+
tracing::info!(
1006+
path = %agents_md_path.display(),
1007+
"Auto-loaded AGENTS.md from workspace root"
1008+
);
1009+
prompt_slots.extra = match prompt_slots.extra {
1010+
Some(existing) => Some(format!("{}\n\n# Project Instructions (AGENTS.md)\n\n{}", existing, content)),
1011+
None => Some(format!("# Project Instructions (AGENTS.md)\n\n{}", content)),
1012+
};
1013+
}
1014+
Ok(_) => {
1015+
tracing::debug!(
1016+
path = %agents_md_path.display(),
1017+
"AGENTS.md exists but is empty — skipping"
1018+
);
1019+
}
1020+
Err(e) => {
1021+
tracing::warn!(
1022+
path = %agents_md_path.display(),
1023+
error = %e,
1024+
"Failed to read AGENTS.md — skipping"
1025+
);
1026+
}
1027+
}
1028+
}
1029+
10001030
// Build effective skill registry: fork the agent-level registry (builtins + global
10011031
// skill_dirs), then layer session-level skills on top. Forking ensures session skills
10021032
// never pollute the shared agent-level registry.

core/src/lib.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ pub mod hooks;
6666
pub mod llm;
6767
pub mod mcp;
6868
pub mod memory;
69-
pub mod middleware;
7069
pub mod orchestrator;
7170
pub mod permissions;
7271
pub mod planning;
@@ -106,10 +105,6 @@ pub use llm::{
106105
AnthropicClient, Attachment, ContentBlock, ImageSource, LlmClient, LlmResponse, Message,
107106
OpenAiClient, TokenUsage,
108107
};
109-
pub use middleware::{
110-
LoggingMiddleware, Middleware, MiddlewareContext, MiddlewarePipeline, MiddlewareResult,
111-
PermissionMiddleware, SecurityMiddleware, ToolCallInfo,
112-
};
113108
pub use orchestrator::AgentSlot;
114109
pub use prompts::SystemPromptSlots;
115110
pub use queue::{

core/src/middleware/context.rs

Lines changed: 0 additions & 74 deletions
This file was deleted.

core/src/middleware/logging.rs

Lines changed: 0 additions & 43 deletions
This file was deleted.

0 commit comments

Comments
 (0)