Skip to content
This repository was archived by the owner on Jun 1, 2026. It is now read-only.

Commit 08e97cb

Browse files
lapc506claude
andcommitted
Merge feat/phase1-core: Phase 1 COMPLETE (15/15 tasks)
All 15 tasks implemented with TDD: 1. Project scaffolding (pyproject.toml, CI, structure) 2. Shared kernel (types, DomainEvent, EventBus) 3. Domain enums (SessionState, GraphTemplate, EmbeddingTaskType) 4. Value objects (AgentMessage, ModelConfig, ToolResult, MultimodalContent) 5. Domain entities (Session state machine, Persona, Skill, Roadmap) 6. Domain events (8 event types) 7. Domain services (EscalationService, ModelResolver, RoutingService, EvalScoring) 8. Application ports (12 ABCs: hexagonal boundaries) 9. Command/Query handlers (HandleMessage, CreateSession, ResumeHITL, GetSession, ListPersonas) 10. Middleware chain + TracingMiddleware (no-op fallback) 11. Configuration (AgenticSettings, sidecar mode, MCP, Observability, Embedding) 12. gRPC proto + codegen (6 RPCs, streaming) 13. WebSocket adapter (full protocol, heartbeat, errors, session lifecycle) 14. gRPC adapter (AgentService servicer, all RPCs) 15. AgentRuntime composition root (wires everything, starts both transports) 105 tests passing. 81% coverage. Python 3.12+. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2 parents 22b4cc0 + d67a739 commit 08e97cb

22 files changed

Lines changed: 2118 additions & 0 deletions

Makefile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.PHONY: proto test lint typecheck
2+
3+
proto:
4+
python -m grpc_tools.protoc \
5+
-I proto \
6+
--python_out=src/agentic_core/adapters/primary/grpc/generated \
7+
--grpc_python_out=src/agentic_core/adapters/primary/grpc/generated \
8+
--pyi_out=src/agentic_core/adapters/primary/grpc/generated \
9+
proto/agentic_core.proto
10+
11+
test:
12+
pytest --cov=agentic_core --cov-report=term-missing -v
13+
14+
lint:
15+
ruff check src/ tests/
16+
17+
typecheck:
18+
mypy src/agentic_core/

proto/agentic_core.proto

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
syntax = "proto3";
2+
3+
package agentic_core;
4+
5+
service AgentService {
6+
rpc SendMessage(AgentRequest) returns (stream AgentResponse);
7+
rpc CreateSession(CreateSessionRequest) returns (SessionInfo);
8+
rpc GetSession(GetSessionRequest) returns (SessionInfo);
9+
rpc ResumeHITL(HumanResponse) returns (stream AgentResponse);
10+
rpc ListPersonas(Empty) returns (PersonaList);
11+
rpc HealthCheck(Empty) returns (HealthStatus);
12+
}
13+
14+
message Empty {}
15+
16+
message AgentRequest {
17+
string session_id = 1;
18+
string persona_id = 2;
19+
string content = 3;
20+
string user_id = 4;
21+
string trace_id = 5;
22+
}
23+
24+
message CreateSessionRequest {
25+
string persona_id = 1;
26+
string user_id = 2;
27+
}
28+
29+
message GetSessionRequest {
30+
string session_id = 1;
31+
}
32+
33+
message HumanResponse {
34+
string session_id = 1;
35+
string content = 2;
36+
}
37+
38+
message SessionInfo {
39+
string session_id = 1;
40+
string persona_id = 2;
41+
string user_id = 3;
42+
string state = 4;
43+
string created_at = 5;
44+
}
45+
46+
message PersonaList {
47+
repeated PersonaInfo personas = 1;
48+
}
49+
50+
message PersonaInfo {
51+
string name = 1;
52+
string role = 2;
53+
string description = 3;
54+
string graph_template = 4;
55+
}
56+
57+
message HealthStatus {
58+
bool healthy = 1;
59+
string version = 2;
60+
int32 active_sessions = 3;
61+
}
62+
63+
message AgentResponse {
64+
oneof payload {
65+
StreamToken token = 1;
66+
StreamEnd end = 2;
67+
HumanEscalation escalation = 3;
68+
AudioChunk audio = 4;
69+
ErrorDetail error = 5;
70+
}
71+
}
72+
73+
message StreamToken {
74+
string session_id = 1;
75+
string token = 2;
76+
}
77+
78+
message StreamEnd {
79+
string session_id = 1;
80+
}
81+
82+
message HumanEscalation {
83+
string session_id = 1;
84+
string prompt = 2;
85+
}
86+
87+
message AudioChunk {
88+
string session_id = 1;
89+
bytes data = 2;
90+
}
91+
92+
message ErrorDetail {
93+
string session_id = 1;
94+
string code = 2;
95+
string message = 3;
96+
}

src/agentic_core/adapters/primary/grpc/generated/agentic_core_pb2.py

Lines changed: 66 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
from google.protobuf.internal import containers as _containers
2+
from google.protobuf import descriptor as _descriptor
3+
from google.protobuf import message as _message
4+
from collections.abc import Iterable as _Iterable, Mapping as _Mapping
5+
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
6+
7+
DESCRIPTOR: _descriptor.FileDescriptor
8+
9+
class Empty(_message.Message):
10+
__slots__ = ()
11+
def __init__(self) -> None: ...
12+
13+
class AgentRequest(_message.Message):
14+
__slots__ = ("session_id", "persona_id", "content", "user_id", "trace_id")
15+
SESSION_ID_FIELD_NUMBER: _ClassVar[int]
16+
PERSONA_ID_FIELD_NUMBER: _ClassVar[int]
17+
CONTENT_FIELD_NUMBER: _ClassVar[int]
18+
USER_ID_FIELD_NUMBER: _ClassVar[int]
19+
TRACE_ID_FIELD_NUMBER: _ClassVar[int]
20+
session_id: str
21+
persona_id: str
22+
content: str
23+
user_id: str
24+
trace_id: str
25+
def __init__(self, session_id: _Optional[str] = ..., persona_id: _Optional[str] = ..., content: _Optional[str] = ..., user_id: _Optional[str] = ..., trace_id: _Optional[str] = ...) -> None: ...
26+
27+
class CreateSessionRequest(_message.Message):
28+
__slots__ = ("persona_id", "user_id")
29+
PERSONA_ID_FIELD_NUMBER: _ClassVar[int]
30+
USER_ID_FIELD_NUMBER: _ClassVar[int]
31+
persona_id: str
32+
user_id: str
33+
def __init__(self, persona_id: _Optional[str] = ..., user_id: _Optional[str] = ...) -> None: ...
34+
35+
class GetSessionRequest(_message.Message):
36+
__slots__ = ("session_id",)
37+
SESSION_ID_FIELD_NUMBER: _ClassVar[int]
38+
session_id: str
39+
def __init__(self, session_id: _Optional[str] = ...) -> None: ...
40+
41+
class HumanResponse(_message.Message):
42+
__slots__ = ("session_id", "content")
43+
SESSION_ID_FIELD_NUMBER: _ClassVar[int]
44+
CONTENT_FIELD_NUMBER: _ClassVar[int]
45+
session_id: str
46+
content: str
47+
def __init__(self, session_id: _Optional[str] = ..., content: _Optional[str] = ...) -> None: ...
48+
49+
class SessionInfo(_message.Message):
50+
__slots__ = ("session_id", "persona_id", "user_id", "state", "created_at")
51+
SESSION_ID_FIELD_NUMBER: _ClassVar[int]
52+
PERSONA_ID_FIELD_NUMBER: _ClassVar[int]
53+
USER_ID_FIELD_NUMBER: _ClassVar[int]
54+
STATE_FIELD_NUMBER: _ClassVar[int]
55+
CREATED_AT_FIELD_NUMBER: _ClassVar[int]
56+
session_id: str
57+
persona_id: str
58+
user_id: str
59+
state: str
60+
created_at: str
61+
def __init__(self, session_id: _Optional[str] = ..., persona_id: _Optional[str] = ..., user_id: _Optional[str] = ..., state: _Optional[str] = ..., created_at: _Optional[str] = ...) -> None: ...
62+
63+
class PersonaList(_message.Message):
64+
__slots__ = ("personas",)
65+
PERSONAS_FIELD_NUMBER: _ClassVar[int]
66+
personas: _containers.RepeatedCompositeFieldContainer[PersonaInfo]
67+
def __init__(self, personas: _Optional[_Iterable[_Union[PersonaInfo, _Mapping]]] = ...) -> None: ...
68+
69+
class PersonaInfo(_message.Message):
70+
__slots__ = ("name", "role", "description", "graph_template")
71+
NAME_FIELD_NUMBER: _ClassVar[int]
72+
ROLE_FIELD_NUMBER: _ClassVar[int]
73+
DESCRIPTION_FIELD_NUMBER: _ClassVar[int]
74+
GRAPH_TEMPLATE_FIELD_NUMBER: _ClassVar[int]
75+
name: str
76+
role: str
77+
description: str
78+
graph_template: str
79+
def __init__(self, name: _Optional[str] = ..., role: _Optional[str] = ..., description: _Optional[str] = ..., graph_template: _Optional[str] = ...) -> None: ...
80+
81+
class HealthStatus(_message.Message):
82+
__slots__ = ("healthy", "version", "active_sessions")
83+
HEALTHY_FIELD_NUMBER: _ClassVar[int]
84+
VERSION_FIELD_NUMBER: _ClassVar[int]
85+
ACTIVE_SESSIONS_FIELD_NUMBER: _ClassVar[int]
86+
healthy: bool
87+
version: str
88+
active_sessions: int
89+
def __init__(self, healthy: bool = ..., version: _Optional[str] = ..., active_sessions: _Optional[int] = ...) -> None: ...
90+
91+
class AgentResponse(_message.Message):
92+
__slots__ = ("token", "end", "escalation", "audio", "error")
93+
TOKEN_FIELD_NUMBER: _ClassVar[int]
94+
END_FIELD_NUMBER: _ClassVar[int]
95+
ESCALATION_FIELD_NUMBER: _ClassVar[int]
96+
AUDIO_FIELD_NUMBER: _ClassVar[int]
97+
ERROR_FIELD_NUMBER: _ClassVar[int]
98+
token: StreamToken
99+
end: StreamEnd
100+
escalation: HumanEscalation
101+
audio: AudioChunk
102+
error: ErrorDetail
103+
def __init__(self, token: _Optional[_Union[StreamToken, _Mapping]] = ..., end: _Optional[_Union[StreamEnd, _Mapping]] = ..., escalation: _Optional[_Union[HumanEscalation, _Mapping]] = ..., audio: _Optional[_Union[AudioChunk, _Mapping]] = ..., error: _Optional[_Union[ErrorDetail, _Mapping]] = ...) -> None: ...
104+
105+
class StreamToken(_message.Message):
106+
__slots__ = ("session_id", "token")
107+
SESSION_ID_FIELD_NUMBER: _ClassVar[int]
108+
TOKEN_FIELD_NUMBER: _ClassVar[int]
109+
session_id: str
110+
token: str
111+
def __init__(self, session_id: _Optional[str] = ..., token: _Optional[str] = ...) -> None: ...
112+
113+
class StreamEnd(_message.Message):
114+
__slots__ = ("session_id",)
115+
SESSION_ID_FIELD_NUMBER: _ClassVar[int]
116+
session_id: str
117+
def __init__(self, session_id: _Optional[str] = ...) -> None: ...
118+
119+
class HumanEscalation(_message.Message):
120+
__slots__ = ("session_id", "prompt")
121+
SESSION_ID_FIELD_NUMBER: _ClassVar[int]
122+
PROMPT_FIELD_NUMBER: _ClassVar[int]
123+
session_id: str
124+
prompt: str
125+
def __init__(self, session_id: _Optional[str] = ..., prompt: _Optional[str] = ...) -> None: ...
126+
127+
class AudioChunk(_message.Message):
128+
__slots__ = ("session_id", "data")
129+
SESSION_ID_FIELD_NUMBER: _ClassVar[int]
130+
DATA_FIELD_NUMBER: _ClassVar[int]
131+
session_id: str
132+
data: bytes
133+
def __init__(self, session_id: _Optional[str] = ..., data: _Optional[bytes] = ...) -> None: ...
134+
135+
class ErrorDetail(_message.Message):
136+
__slots__ = ("session_id", "code", "message")
137+
SESSION_ID_FIELD_NUMBER: _ClassVar[int]
138+
CODE_FIELD_NUMBER: _ClassVar[int]
139+
MESSAGE_FIELD_NUMBER: _ClassVar[int]
140+
session_id: str
141+
code: str
142+
message: str
143+
def __init__(self, session_id: _Optional[str] = ..., code: _Optional[str] = ..., message: _Optional[str] = ...) -> None: ...

0 commit comments

Comments
 (0)