Skip to content

feat: HouseholdEntity Actor — Living with AI Demo#129

Merged
8 commits merged intodevfrom
feature/living-with-ai
Apr 10, 2026
Merged

feat: HouseholdEntity Actor — Living with AI Demo#129
8 commits merged intodevfrom
feature/living-with-ai

Conversation

@eanzhao
Copy link
Copy Markdown
Contributor

@eanzhao eanzhao commented Apr 7, 2026

Summary

Closes #97

  • Add HouseholdEntity actor inheriting AIGAgentBase<HouseholdEntityState> with Perceive-Reason-Act loop
  • Define Protobuf state: EnvironmentSnapshot, ActionRecord, MemoryEntry, SafetyState + domain events
  • Implement trigger conditions (temperature >2°C, light >30%, motion, camera scene, chat, heartbeat)
  • Implement safety guardrails (kill switch, ≤3 actions/min, 30s debounce, mode checks)
  • Dynamic system prompt injection with environment, recent actions, and memories
  • 22 unit tests covering trigger logic, safety checks, state transitions, time periods, and Protobuf serialization

Architecture decisions

  • Inherits AIGAgentBase (not RoleGAgent): HouseholdEntity is driven by stream events, not ChatRequestEvent
  • Project location: agents/Aevatar.GAgents.Household/ following existing agent naming conventions
  • NyxID tools: Full auto-injection via IAgentToolSource; system prompt guides LLM to relevant tools
  • State persistence: Reasoning + action events persisted; sensor updates are memory-only (high frequency)

Files added

  • agents/Aevatar.GAgents.Household/ — Actor implementation (5 files)
  • test/Aevatar.GAgents.Household.Tests/ — Unit tests (22 tests, all passing)
  • aevatar.slnx — Solution updated with both projects

Test plan

  • dotnet build succeeds
  • dotnet test — 22/22 tests pass
  • Deploy to testnet and verify Actor → NyxID → HA link (G1 milestone)
  • Integrate with sensor/camera stream events (follow-up PR)

🤖 Generated with Claude Code

eanzhao and others added 4 commits April 6, 2026 17:37
Implement the channel runtime architecture (issue #113) that allows
Aevatar to receive bot platform webhooks directly instead of relying
on NyxID as a relay middleman. Nyx remains the credentialed outbound
provider for sending messages.

New project: agents/Aevatar.GAgents.ChannelRuntime with:
- IPlatformAdapter abstraction for multi-platform support
- LarkPlatformAdapter: URL verification + im.message.receive_v1 parsing
- ChannelBotRegistrationStore: persistent protobuf-based config store
- ChannelCallbackEndpoints: webhook receiver + registration CRUD
- Async acknowledge-then-respond pattern (platforms have short timeouts)
- Outbound replies via NyxIdApiClient.ProxyRequestAsync

Existing NyxID relay path (/api/webhooks/nyxid-relay) is untouched.

Closes #113

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…apter)

15 tests covering:
- ChannelBotRegistrationStore: CRUD operations, persistence, edge cases
- LarkPlatformAdapter: URL verification, message parsing, bot filtering,
  empty text handling, missing header handling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ensure that session completion response is published immediately to consumers (relay, SSE) before attempting persistence. Persistence is now best-effort with logging for failures, preventing concurrency conflicts from blocking the reply to the user.
Standalone ASP.NET minimal API server at tools/Aevatar.Tools.MockNyxId
that simulates the NyxID external service. Enables end-to-end testing
of channel runtime, LLM calls, and proxy operations without a real
NyxID instance.

Endpoints:
- GET  /api/v1/users/me — mock user info
- POST /api/v1/auth/test-token — issue test JWTs
- GET  /api/v1/proxy/services — service discovery (api-lark-bot, api-github)
- *    /api/v1/proxy/s/{slug}/{**path} — catch-all proxy with Lark/Telegram-aware responses
- POST /api/v1/llm/gateway/v1/chat/completions — OpenAI-compatible (streaming + non-streaming)

Usage:
  dotnet run --project tools/Aevatar.Tools.MockNyxId
  export Aevatar__NyxId__Authority=http://localhost:5199

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 78cc194965

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +34 to +38
typeProp.GetString() == "url_verification")
{
var challenge = root.TryGetProperty("challenge", out var ch) ? ch.GetString() : null;
_logger.LogInformation("Lark URL verification challenge received");
return Results.Json(new { challenge });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Verify Lark callback token before accepting challenge

In TryHandleVerificationAsync, the challenge is echoed without checking the incoming token/signature against registration.VerificationToken. This leaves the webhook path effectively unauthenticated: anyone who can hit the callback URL can forge Lark payloads and drive bot traffic/costs. The registration model already stores a verification token, so the adapter should reject verification and message callbacks that fail authenticity checks.

Useful? React with 👍 / 👎.


try
{
var actorId = $"channel-{inbound.Platform}-{inbound.ConversationId}";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include registration ID in channel actor key

ProcessAndReplyAsync keys chat actors only by platform + conversationId. If two registrations (different scope_id/Nyx token) receive the same platform chat ID, they will reuse one NyxIdChatGAgent, mixing history and tool context across registrations. Actor identity should include a registration-scoped dimension (for example registration.Id) to avoid cross-bot/tenant state bleed.

Useful? React with 👍 / 👎.

Comment on lines +138 to +142
if (payload.Is(TextMessageContentEvent.Descriptor))
{
var evt = payload.Unpack<TextMessageContentEvent>();
if (!string.IsNullOrEmpty(evt.Delta))
responseBuilder.Append(evt.Delta);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Filter subscribed stream events to the active request

The subscription callback appends every TextMessageContentEvent from the actor and completes on any TextMessageEndEvent without checking session_id/request correlation. When two callbacks for the same actor overlap, one request can consume the other request’s stream and return the wrong reply. The handler should gate on the current request’s correlation key before appending or completing.

Useful? React with 👍 / 👎.

eanzhao and others added 2 commits April 7, 2026 15:46
1. Verify Lark callback token before accepting verification challenges
   and event callbacks — rejects forged payloads when a verification
   token is configured on the registration.

2. Include registration ID in channel actor key to prevent cross-tenant
   state bleed when two registrations share the same platform chat ID.

3. Filter subscribed stream events by session_id so overlapping
   callbacks for the same actor cannot consume each other's responses.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the autonomous home AI agent as described in issue #97.
HouseholdEntity inherits AIGAgentBase with a Perceive-Reason-Act loop
driven by stream events (sensor, camera, chat, heartbeat) with safety
guardrails (kill switch, rate limiting, debounce) and dynamic context
injection into LLM system prompt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@eanzhao eanzhao force-pushed the feature/living-with-ai branch from cd25bc0 to 13958e2 Compare April 7, 2026 07:47
eanzhao and others added 2 commits April 7, 2026 17:31
Implements the Agent-as-Tool pattern: NyxIdChatGAgent's LLM can call
the "household" tool to dispatch home automation requests to a
HouseholdEntity actor.

- HouseholdEntityTool: IAgentTool that dispatches HouseholdChatEvent
  to HouseholdEntity via IActorRuntime, returns environment state and
  last action as JSON
- HouseholdEntityToolSource: IAgentToolSource for auto-discovery
- ServiceCollectionExtensions.AddHouseholdEntityTools() for DI
- 10 new tests (32 total)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@eanzhao eanzhao closed this pull request by merging all changes into dev in fc43d97 Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HouseholdEntity Actor 实现 — Living with AI Demo / HouseholdEntity Actor Implementation

1 participant