Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions agent_core/core/impl/action/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Execution-scoped context for in-process actions.

``current_input_data`` holds the full ``input_data`` dict of the action
currently executing in this context. It exists so cross-cutting helpers
deep inside an action's call tree (e.g. multi-account routing reading the
``account`` hint) can see routing keys without threading them through
every action function signature.

Scope rules:
- Set only by the internal executors (``_atomic_action_internal*``),
reset in a ``finally`` — never leaks across actions.
- Sync actions run in a thread pool where the caller's context does NOT
propagate, so the executor wraps the call and sets the var inside the
worker thread (see ``run_with_input_context``).
- Sandboxed (subprocess) actions cannot see it at all — helpers must
treat a ``None`` value as "no context available".
"""

from __future__ import annotations

from contextvars import ContextVar
from typing import Any, Callable, Dict, Optional

current_input_data: ContextVar[Optional[Dict[str, Any]]] = ContextVar(
"current_input_data", default=None
)


def run_with_input_context(
function_to_call: Callable[[dict], dict], input_data: dict
) -> dict:
"""Call a sync action with ``current_input_data`` set for its duration.

Used as the thread-pool target: the worker thread has its own context,
so the var must be set (and reset) inside the thread, not the caller.
"""
token = current_input_data.set(input_data)
try:
return function_to_call(input_data)
finally:
current_input_data.reset(token)
23 changes: 19 additions & 4 deletions agent_core/core/impl/action/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,9 @@ def _atomic_action_internal(
"The action_code string did not define a callable Python function."
)

execution_result = function_to_call(input_data)
from agent_core.core.impl.action.context import run_with_input_context

execution_result = run_with_input_context(function_to_call, input_data)
return execution_result

except Exception as e:
Expand Down Expand Up @@ -618,16 +620,29 @@ async def _atomic_action_internal_async(
"The action_code string did not define a callable Python function."
)

from agent_core.core.impl.action.context import (
current_input_data,
run_with_input_context,
)

# Check if the function is async (coroutine function)
if inspect.iscoroutinefunction(function_to_call):
logger.debug(f"[ASYNC] Action '{action_name}' is async, awaiting directly")
execution_result = await function_to_call(input_data)
ctx_token = current_input_data.set(input_data)
try:
execution_result = await function_to_call(input_data)
finally:
current_input_data.reset(ctx_token)
else:
# Sync function - run in thread pool to avoid blocking
# Sync function - run in thread pool to avoid blocking. The
# worker thread doesn't inherit this context, so the wrapper
# sets current_input_data inside the thread.
logger.debug(
f"[SYNC] Action '{action_name}' is sync, running in thread pool"
)
thread_future = THREAD_POOL.submit(function_to_call, input_data)
thread_future = THREAD_POOL.submit(
run_with_input_context, function_to_call, input_data
)
try:
execution_result = await asyncio.wrap_future(thread_future)
except asyncio.CancelledError:
Expand Down
5 changes: 1 addition & 4 deletions agent_core/core/impl/action/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,7 @@ async def execute_action(
# re-execute work the ledger shows as already completed (or as
# interrupted mid-flight, where the effect may have happened).
idem_key = None
# if getattr(action, "irreversible", False) and self._idempotency_guard:

# TODO: Temporary turning idempotency guard off.
if 1 == 0:
if getattr(action, "irreversible", False) and self._idempotency_guard:
try:
decision = self._idempotency_guard.begin(
action.name, input_data, session_id
Expand Down
21 changes: 20 additions & 1 deletion agent_core/core/prompts/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@

Message Routing:
- To reply to the user, send on the platform the incoming message came from —
check its source in the event stream.
check its source in the event stream. An event labeled just "user message"
(no platform tag) was typed in the local CraftBot interface: reply with
send_message, NOT a platform send action, even if earlier turns in this
session came from an external platform.
- To act on a platform the user explicitly names, use that platform's send
action (load its action set first if needed).
- send_message and send_message_with_attachment ONLY records to the local
Expand All @@ -107,6 +110,22 @@
3. Read configuration of your own in app/config/.
- Only ask the user if all three sources fail to provide the answer.

Multi-Account Integrations:
- Integrations can hold several connected accounts (e.g. a work and a school
Gmail). Every integration action takes an optional "account" input: an
email/identity, the user's nickname for the account, or any unique
fragment of either. Omitted = the primary account.
- When the user names an account in ANY form ("my school calendar", "the
work inbox", "from my personal email"), extract that qualifier into
"account". Never silently default to primary when a qualifier is present.
- If an account hint doesn't resolve, the action returns an error listing
the connected accounts — pick the right one from that list or ask the
user; do not retry the same hint.
- IDs are account-scoped: a message/event/file id returned with
account="work" must be passed back with account="work" on follow-ups.
- For irreversible actions (send, delete, clear) with multiple accounts
connected and no qualifier in the request: ask which account first.

Critical Rules:
- The selected action MUST be from the actions list. If none suitable, set
action_name to "" (empty string).
Expand Down
4 changes: 2 additions & 2 deletions agent_file_system/AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@ Editing any of these triggers re-indexing via [agent_core/core/impl/memory/memor
- Purpose: complete chronological event log. Append-only.
- Write access: EventStreamManager. Hard rule: DO NOT edit.
- Read pattern: `read_file` / `grep_files` for self-troubleshooting. See `## Errors` for log workflow.
- Format: `[YYYY-MM-DD HH:MM:SS] [event_type]: payload`. Multi-line payloads continue on subsequent lines.
- Format: `[YYYY/MM/DD HH:MM:SS] [event_type]: payload`. Multi-line payloads continue on subsequent lines.
- Auto-rotated when size threshold is exceeded.

### EVENT_UNPROCESSED.md
Expand Down Expand Up @@ -3090,7 +3090,7 @@ This list is opinion, not authoritative. The user has the final say.
Memory is your long-term recall. It is RAG-backed (relevance search over MEMORY.md and a few other files), not text-grep. Items reach MEMORY.md only after the daily memory-processing pipeline distills them from the event stream. You do NOT write MEMORY.md directly.

Two ways memory reaches you:
- **Automatic injection (passive).** On every user message, the most relevant memories (top 5, relevance ≥ 0.5) are retrieved and dropped into your context as a `relevant_memories` event — one line per pointer: `- [file_path] section_path: summary (relevance: 0.XX)`. If nothing clears the threshold, no event is emitted. You do NOT need to call `memory_search` just to see what you already know. Each `summary` is a TRUNCATED preview (a pointer), not the full memory: it is a snippet centred on the words that matched your query, and a leading/trailing `...` marks text that was cut. Treat these as leads, not complete records — if a preview is on-topic but clipped where it matters, expand it with `memory_search` or by reading the source file before you rely on it.
- **Automatic injection (passive).** On every user message, the most relevant memories (top 5, relevance ≥ 0.5) are retrieved and dropped into your context as a `relevant_memories` event — one line per pointer: `- [file_path] section_path: summary (relevance: 0.XX)`. If nothing clears the threshold, no event is emitted. You do NOT need to call `memory_search` just to see what you already know.
- **`memory_search` action (active).** Use it when you need to dig deeper on a specific question mid-run, beyond what got auto-injected.

Code: [agent_core/core/impl/memory/manager.py](agent_core/core/impl/memory/manager.py) (`MemoryManager`), [agent_core/core/impl/memory/memory_file_watcher.py](agent_core/core/impl/memory/memory_file_watcher.py) (incremental re-indexing), [app/data/action/memory_search.py](app/data/action/memory_search.py) (action).
Expand Down
13 changes: 0 additions & 13 deletions agent_file_system/ENTITIES.md

This file was deleted.

25 changes: 16 additions & 9 deletions agent_file_system/GLOBAL_LIVING_UI.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,21 @@ Per-project settings from Phase 0 Q&A override these when they conflict.
- Text must have sufficient contrast against background (dark text on light backgrounds, light text on dark backgrounds)
- Never use light text on light backgrounds or dark text on dark backgrounds

## Optional Rules

- [x] Enable drag-and-drop for reordering items
- [x] Add keyboard shortcuts for common actions
- [x] Show item count badges on categories/sections
- [x] Add search/filter bar to all list views
- [x] Support bulk selection and batch operations
- [ ] Enable dark mode only (ignore system preference)
- [ ] Add animations and transitions to UI interactions
- [ ] Show timestamps on all items (created/updated)
- [ ] Enable infinite scroll instead of pagination
- [ ] Add undo/redo support for user actions
- [ ] Show breadcrumb navigation for nested views

## Custom Rules

<!-- Add your own rules below as bullet lines -->
<!-- Example: - All lists must support search/filter -->
- Enable drag-and-drop for reordering items
- Add keyboard shortcuts for common actions
- Show item count badges on categories/sections
- Add search/filter bar to all list views
- Support bulk selection and batch operations
- Add animations and transitions to UI interactions
- Add undo/redo support for user actions
<!-- Add your own rules below as checkbox lines -->
<!-- Example: - [x] All lists must support search/filter -->
Loading