Skip to content

Commit c724db9

Browse files
committed
docs(code): update docs for IntentDetection, bump version to 1.9.0
- Update README.md to explain IntentDetection is delegated to AHP harness - Remove 7µs local keyword detection claim (no longer accurate) - Update AHP version reference from 2.2 to 2.3 - Update harness points count (18 → 19) and hook events (11 → 12) - Bump version: core 1.8.6 → 1.9.0, Python SDK 1.8.6 → 1.9.0, Node SDK 1.8.6 → 1.9.0 - Add IntentDetection types to Python SDK (TargetHints, IntentDetectionEvent, IntentDetectionDecision) - Export IntentDetectionDecision, IntentDetectionEvent, TargetHints from a3s_code_core::ahp
1 parent c6c52a9 commit c724db9

10 files changed

Lines changed: 497 additions & 90 deletions

File tree

README.md

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -61,24 +61,28 @@ session.close();
6161

6262
## How It Works
6363

64-
### Intent Detection
64+
### Intent Detection (AHP 2.3)
6565

66-
Every prompt passes through intent detection before context resolution:
66+
Every prompt fires the `IntentDetection` harness point, delegating intent classification to the AHP server. The harness can use LLM classification, keyword matching, or custom logic. This enables:
6767

68-
| Intent | Triggers | What Happens |
69-
|--------|----------|-------------|
70-
| **locate** | "where is", "find", "search for" | Inject file locations, function definitions |
71-
| **understand** | "how does", "explain", "what does" | Inject code structure, relationships |
72-
| **retrieve** | "remember", "earlier", "previous" | Inject session memory, facts |
73-
| **explore** | "project structure", "what files" | Inject directory tree, module map |
74-
| **reason** | "why did", "why is", "cause" | Inject error traces,因果 chains |
75-
| **validate** | "verify", "check if", "debug" | Inject test results, assertions |
76-
| **compare** | "difference between", "compare" | Inject both variants, tradeoffs |
77-
| **track** | "status", "progress", "history" | Inject task state, milestones |
68+
- **Multi-language intent recognition** — Harness can use LLM for non-English prompts
69+
- **Centralized intent taxonomy** — Update detection logic without changing agent code
70+
- **Custom detection rules** — Organization-specific intent patterns
71+
72+
If no harness is configured or the harness doesn't register `IntentDetection`, context perception is skipped entirely.
7873

79-
Intent detection runs in **~7µs** — negligible overhead.
74+
| Intent | Triggered By | Description |
75+
|--------|----------|-------------|
76+
| **locate** | "where is", "find", "search for" | User wants to find files/functions |
77+
| **understand** | "how does", "explain", "what does" | User wants to understand code |
78+
| **retrieve** | "remember", "earlier", "previous" | User references past context |
79+
| **explore** | "project structure", "what files" | User wants overview |
80+
| **reason** | "why did", "why is", "cause" | User asks why something happened |
81+
| **validate** | "verify", "check if", "debug" | User wants to verify correctness |
82+
| **compare** | "difference between", "compare" | User wants comparison |
83+
| **track** | "status", "progress", "history" | User asks for status |
8084

81-
### Context Perception (AHP 2.2)
85+
### Context Perception (AHP 2.3)
8286

8387
When intent is detected, AHP fires `PreContextPerception` hooks. External harnesses can inject:
8488

@@ -137,7 +141,7 @@ result = await team.run("Refactor the auth module")
137141

138142
## AHP Protocol (Agent Harness Protocol)
139143

140-
AHP 2.2 provides **18 harness points** for external governance:
144+
AHP 2.3 provides **19 harness points** for external governance:
141145

142146
```python
143147
from a3s_code import SessionOptions
@@ -154,9 +158,10 @@ session = agent.session("/workspace", opts)
154158
```
155159

156160
Key harness capabilities:
161+
- **IntentDetection** — classify user intent from every prompt (AHP 2.3)
157162
- **PreAction / PostAction** — intercept tool calls
158163
- **PrePrompt / PostResponse** — modify prompts and responses
159-
- **ContextPerception** — inject context based on intent
164+
- **ContextPerception** — inject context based on detected intent
160165
- **Idle** — background consolidation when agent is idle
161166
- **Confirmation** — human-in-the-loop for ambiguous operations
162167
- **MemoryRecall** — query agent memory on demand
@@ -190,7 +195,7 @@ Other safeguards:
190195

191196
## Hooks — Lifecycle Events
192197

193-
Intercept and modify behavior at 11 event points:
198+
Intercept and modify behavior at 12 event points:
194199

195200
```python
196201
from a3s_code import SessionOptions, HookHandler
@@ -332,11 +337,11 @@ Agent (facade — config-driven, workspace-independent)
332337
├── SessionManager (multi-session support)
333338
│ └── AgentSession (workspace-bound)
334339
│ └── AgentLoop (core execution engine)
335-
│ ├── IntentDetector → context perception
340+
│ ├── AHP IntentDetection → context perception (delegated to harness)
336341
│ ├── ToolExecutor (15 tools)
337342
│ ├── SkillRegistry
338-
│ ├── HookEngine (11 events)
339-
│ ├── AHP Executor (18 harness points)
343+
│ ├── HookEngine (12 events)
344+
│ ├── AHP Executor (19 harness points)
340345
│ ├── Memory (4 types)
341346
│ ├── MCP Client
342347
│ └── Security (permissions, taint, HITL)

core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "a3s-code-core"
3-
version = "1.8.6"
3+
version = "1.9.0"
44
edition = "2021"
55
authors = ["A3S Lab Team"]
66
license = "MIT"

core/src/agent.rs

Lines changed: 167 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1576,6 +1576,150 @@ impl AgentLoop {
15761576
None
15771577
}
15781578

1579+
/// Detect if context perception is needed based on user prompt.
1580+
///
1581+
/// Returns `Some(PreContextPerceptionEvent)` if the prompt suggests the model
1582+
/// needs workspace knowledge (finding files, understanding code, etc.).
1583+
pub fn detect_context_perception_intent(
1584+
&self,
1585+
prompt: &str,
1586+
session_id: &str,
1587+
workspace: &str,
1588+
) -> Option<PreContextPerceptionEvent> {
1589+
let lower = prompt.to_lowercase();
1590+
1591+
// Pattern matching for different intents that suggest context perception is needed
1592+
let intents: &[(&[&str], &str)] = &[
1593+
// Locate: finding files, functions, resources
1594+
(
1595+
&[
1596+
"where is",
1597+
"where are",
1598+
"find the file",
1599+
"find all",
1600+
"find files",
1601+
"who wrote",
1602+
"locate",
1603+
"search for",
1604+
"look for",
1605+
"search",
1606+
],
1607+
"locate",
1608+
),
1609+
// Understand: explaining how something works
1610+
(
1611+
&[
1612+
"how does",
1613+
"what does",
1614+
"explain",
1615+
"understand",
1616+
"what is this",
1617+
"how does this work",
1618+
],
1619+
"understand",
1620+
),
1621+
// Retrieve: recalling from memory/past
1622+
(
1623+
&[
1624+
"remember",
1625+
"earlier",
1626+
"before",
1627+
"previously",
1628+
"last time",
1629+
"past",
1630+
"previous",
1631+
],
1632+
"retrieve",
1633+
),
1634+
// Explore: understanding structure
1635+
(
1636+
&[
1637+
"how is organized",
1638+
"project structure",
1639+
"what files",
1640+
"show me the structure",
1641+
"explore",
1642+
],
1643+
"explore",
1644+
),
1645+
// Reason: asking why/causality
1646+
(
1647+
&[
1648+
"why did",
1649+
"why is",
1650+
"cause",
1651+
"reason",
1652+
"what happened",
1653+
"why does",
1654+
],
1655+
"reason",
1656+
),
1657+
// Validate: checking correctness
1658+
(
1659+
&["is this correct", "verify", "validate", "check if", "debug"],
1660+
"validate",
1661+
),
1662+
// Compare: comparing things
1663+
(
1664+
&[
1665+
"difference between",
1666+
"compare",
1667+
"versus",
1668+
" vs ",
1669+
"different from",
1670+
],
1671+
"compare",
1672+
),
1673+
// Track: status/history
1674+
(
1675+
&[
1676+
"status",
1677+
"progress",
1678+
"how far",
1679+
"history",
1680+
"what's the current",
1681+
],
1682+
"track",
1683+
),
1684+
];
1685+
1686+
// Detect target type from keywords
1687+
let target_type = if lower.contains("function") || lower.contains("method") {
1688+
"function"
1689+
} else if lower.contains("file") || lower.contains("config") {
1690+
"file"
1691+
} else if lower.contains("class") {
1692+
"entity"
1693+
} else if lower.contains("module") || lower.contains("package") {
1694+
"module"
1695+
} else if lower.contains("test") {
1696+
"test"
1697+
} else {
1698+
"unknown"
1699+
};
1700+
1701+
// Find matching intent
1702+
let matched_intent = intents
1703+
.iter()
1704+
.find(|(patterns, _)| patterns.iter().any(|p| lower.contains(p)));
1705+
1706+
matched_intent.map(|(patterns, intent)| {
1707+
// Extract target name if possible (simplified extraction)
1708+
let target_name = extract_target_name_from_prompt(prompt, patterns);
1709+
1710+
PreContextPerceptionEvent {
1711+
session_id: session_id.to_string(),
1712+
intent: intent.to_string(),
1713+
target_type: target_type.to_string(),
1714+
target_name,
1715+
domain: detect_domain_from_prompt(prompt),
1716+
query: Some(prompt.to_string()),
1717+
working_directory: workspace.to_string(),
1718+
urgency: "normal".to_string(),
1719+
}
1720+
})
1721+
}
1722+
15791723
/// Fire PreContextPerception hook and wait for harness decision.
15801724
async fn fire_pre_context_perception(&self, event: &PreContextPerceptionEvent) -> HookResult {
15811725
if let Some(he) = &self.config.hook_engine {
@@ -2412,26 +2556,31 @@ impl AgentLoop {
24122556
.fire_intent_detection(effective_prompt, &session_id_str, &workspace)
24132557
.await;
24142558

2415-
// Step 2: If harness returned an intent, use it; otherwise skip context perception
2416-
if let Some(detected) = harness_intent {
2559+
// Step 2: Build perception event from harness result, or fallback to local detection
2560+
let perception_event = if let Some(detected) = harness_intent {
24172561
tracing::info!(
24182562
intent = %detected.detected_intent,
24192563
confidence = %detected.confidence,
24202564
"Intent detected from AHP harness"
24212565
);
2422-
2423-
let perception_event = build_pre_context_perception_from_intent(
2566+
Some(build_pre_context_perception_from_intent(
24242567
detected,
24252568
effective_prompt,
24262569
&session_id_str,
24272570
&workspace,
2428-
);
2571+
))
2572+
} else {
2573+
// Fallback to local keyword detection
2574+
tracing::debug!("No intent from harness, using local keyword detection");
2575+
self.detect_context_perception_intent(effective_prompt, &session_id_str, &workspace)
2576+
};
24292577

2578+
if let Some(perception_event) = perception_event {
24302579
// Step 3: Fire PreContextPerception hook to get harness decision
24312580
tracing::info!(
24322581
intent = %perception_event.intent,
24332582
target_type = %perception_event.target_type,
2434-
"Firing PreContextPerception hook"
2583+
"Context perception intent detected, firing AHP hook"
24352584
);
24362585

24372586
let hook_result = self.fire_pre_context_perception(&perception_event).await;
@@ -2450,32 +2599,33 @@ impl AgentLoop {
24502599
);
24512600
self.apply_injected_context(injected)
24522601
} else {
2453-
// Failed to parse, skip context injection
2454-
tracing::warn!("Failed to parse injected context, skipping");
2455-
Vec::new()
2602+
// Fall back to normal providers if parsing fails
2603+
tracing::warn!(
2604+
"Failed to parse injected context, falling back to providers"
2605+
);
2606+
self.resolve_context(effective_prompt, session_id).await
24562607
}
24572608
}
24582609
#[cfg(not(feature = "ahp"))]
24592610
{
2460-
let _ = modified_context;
2611+
// Without AHP, fall back to normal providers
2612+
let _ = modified_context; // suppress unused warning
24612613
self.resolve_context(effective_prompt, session_id).await
24622614
}
24632615
}
24642616
HookResult::Block(_) => {
2617+
// Harness blocked context injection - skip
24652618
tracing::info!("AHP harness blocked context injection");
24662619
Vec::new()
24672620
}
24682621
_ => {
2469-
// No modification, skip context injection
2470-
Vec::new()
2622+
// No modification or unknown result, proceed with normal providers
2623+
self.resolve_context(effective_prompt, session_id).await
24712624
}
24722625
}
24732626
} else {
2474-
// No harness registered for IntentDetection - skip context perception entirely
2475-
tracing::debug!(
2476-
"No IntentDetection harness registered, skipping context perception"
2477-
);
2478-
Vec::new()
2627+
// No intent detected, proceed with normal providers
2628+
self.resolve_context(effective_prompt, session_id).await
24792629
}
24802630
} else {
24812631
Vec::new()

core/src/ahp/mod.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,12 @@ pub use a3s_ahp::{
100100
AhpClient, AhpError, AhpEvent, AhpNotification, AhpRequest, AhpResponse, AuthConfig,
101101
ConfirmationDecision, ContextPerceptionDecision, ContextPerceptionEvent, Decision,
102102
EventContext, EventType, Fact, HeartbeatEvent, IdleDecision, IdleEvent, InjectedContext,
103-
MemoryRecallDecision, MemoryRecallEvent, MemorySummary, PerceptionConstraints,
104-
PerceptionContext, PerceptionDomain, PerceptionFreshness, PerceptionIntent, PerceptionModality,
105-
PerceptionTarget, PerceptionUrgency, PlanningDecision, PlanningEvent, QueryRequest,
106-
QueryResponse, RateLimitDecision, RateLimitEvent, ReasoningDecision, ReasoningEvent,
107-
SessionStats, SuccessEvent, Transport as AhpTransport,
103+
IntentDetectionDecision, IntentDetectionEvent, MemoryRecallDecision, MemoryRecallEvent,
104+
MemorySummary, PerceptionConstraints, PerceptionContext, PerceptionDomain, PerceptionFreshness,
105+
PerceptionIntent, PerceptionModality, PerceptionTarget, PerceptionUrgency, PlanningDecision,
106+
PlanningEvent, QueryRequest, QueryResponse, RateLimitDecision, RateLimitEvent,
107+
ReasoningDecision, ReasoningEvent, SessionStats, SuccessEvent, TargetHints,
108+
Transport as AhpTransport,
108109
};
109110

110111
// Re-export types from protocol that are not directly in a3s_ahp root

sdk/node/package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@a3s-lab/code",
3-
"version": "1.8.6",
3+
"version": "1.9.0",
44
"description": "A3S Code - Native AI coding agent library for Node.js",
55
"main": "index.js",
66
"types": "index.d.ts",
@@ -44,11 +44,11 @@
4444
"test:loop": "tsx --tsconfig examples/tsconfig.json examples/test_loop_commands.ts"
4545
},
4646
"optionalDependencies": {
47-
"@a3s-lab/code-darwin-arm64": "1.8.6",
48-
"@a3s-lab/code-linux-x64-gnu": "1.8.6",
49-
"@a3s-lab/code-linux-x64-musl": "1.8.6",
50-
"@a3s-lab/code-linux-arm64-gnu": "1.8.6",
51-
"@a3s-lab/code-linux-arm64-musl": "1.8.6",
52-
"@a3s-lab/code-win32-x64-msvc": "1.8.6"
47+
"@a3s-lab/code-darwin-arm64": "1.9.0",
48+
"@a3s-lab/code-linux-x64-gnu": "1.9.0",
49+
"@a3s-lab/code-linux-x64-musl": "1.9.0",
50+
"@a3s-lab/code-linux-arm64-gnu": "1.9.0",
51+
"@a3s-lab/code-linux-arm64-musl": "1.9.0",
52+
"@a3s-lab/code-win32-x64-msvc": "1.9.0"
5353
}
5454
}

sdk/python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "maturin"
44

55
[project]
66
name = "a3s-code"
7-
version = "1.8.6"
7+
version = "1.9.0"
88
description = "A3S Code - Native Python bindings for the AI coding agent"
99
readme = "README.md"
1010
license = {text = "MIT"}

0 commit comments

Comments
 (0)