anycode-py 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
anycode/__init__.py ADDED
@@ -0,0 +1,121 @@
1
+ """AnyCode — scalable multi-agent AI orchestration framework for Python."""
2
+
3
+ from anycode.collaboration.kv_store import InMemoryStore
4
+ from anycode.collaboration.message_bus import MessageBus
5
+ from anycode.collaboration.shared_mem import SharedMemory
6
+ from anycode.collaboration.team import Team
7
+ from anycode.core.agent import Agent
8
+ from anycode.core.orchestrator import AnyCode, TaskSpec
9
+ from anycode.core.pool import AgentPool
10
+ from anycode.core.runner import AgentRunner
11
+ from anycode.core.scheduler import Scheduler
12
+ from anycode.helpers.concurrency_gate import Semaphore
13
+ from anycode.helpers.usage_tracker import EMPTY_USAGE, merge_usage
14
+ from anycode.providers.adapter import create_adapter
15
+ from anycode.tasks.queue import TaskQueue
16
+ from anycode.tasks.task import create_task, get_task_dependency_order, is_task_ready, validate_task_dependencies
17
+ from anycode.tools.built_in import BUILT_IN_TOOLS, register_built_in_tools
18
+ from anycode.tools.executor import ToolExecutor
19
+ from anycode.tools.registry import ToolRegistry, define_tool
20
+ from anycode.types import (
21
+ AgentConfig,
22
+ AgentInfo,
23
+ AgentRunResult,
24
+ AgentState,
25
+ ContentBlock,
26
+ ImageBlock,
27
+ LLMAdapter,
28
+ LLMChatOptions,
29
+ LLMMessage,
30
+ LLMResponse,
31
+ LLMStreamOptions,
32
+ LLMToolDef,
33
+ MemoryEntry,
34
+ MemoryStore,
35
+ OrchestratorConfig,
36
+ OrchestratorEvent,
37
+ PoolStatus,
38
+ RunnerOptions,
39
+ RunResult,
40
+ SchedulingStrategy,
41
+ StreamEvent,
42
+ Task,
43
+ TaskStatus,
44
+ TeamConfig,
45
+ TeamRunResult,
46
+ TextBlock,
47
+ TokenUsage,
48
+ ToolCallRecord,
49
+ ToolDefinition,
50
+ ToolResult,
51
+ ToolResultBlock,
52
+ ToolUseBlock,
53
+ ToolUseContext,
54
+ )
55
+
56
+ __all__ = [
57
+ # Core
58
+ "AnyCode",
59
+ "Agent",
60
+ "AgentRunner",
61
+ "AgentPool",
62
+ "Scheduler",
63
+ "TaskSpec",
64
+ # Providers
65
+ "create_adapter",
66
+ # Collaboration
67
+ "Team",
68
+ "MessageBus",
69
+ "SharedMemory",
70
+ "InMemoryStore",
71
+ # Tasks
72
+ "TaskQueue",
73
+ "create_task",
74
+ "is_task_ready",
75
+ "get_task_dependency_order",
76
+ "validate_task_dependencies",
77
+ # Tools
78
+ "ToolRegistry",
79
+ "ToolExecutor",
80
+ "define_tool",
81
+ "BUILT_IN_TOOLS",
82
+ "register_built_in_tools",
83
+ # Helpers
84
+ "Semaphore",
85
+ "EMPTY_USAGE",
86
+ "merge_usage",
87
+ # Types
88
+ "ContentBlock",
89
+ "TextBlock",
90
+ "ToolUseBlock",
91
+ "ToolResultBlock",
92
+ "ImageBlock",
93
+ "LLMMessage",
94
+ "TokenUsage",
95
+ "LLMResponse",
96
+ "StreamEvent",
97
+ "LLMToolDef",
98
+ "ToolUseContext",
99
+ "AgentInfo",
100
+ "ToolResult",
101
+ "ToolDefinition",
102
+ "AgentConfig",
103
+ "AgentState",
104
+ "ToolCallRecord",
105
+ "AgentRunResult",
106
+ "TeamConfig",
107
+ "TeamRunResult",
108
+ "TaskStatus",
109
+ "Task",
110
+ "OrchestratorEvent",
111
+ "OrchestratorConfig",
112
+ "MemoryEntry",
113
+ "MemoryStore",
114
+ "LLMChatOptions",
115
+ "LLMStreamOptions",
116
+ "LLMAdapter",
117
+ "RunnerOptions",
118
+ "RunResult",
119
+ "PoolStatus",
120
+ "SchedulingStrategy",
121
+ ]
@@ -0,0 +1,6 @@
1
+ from anycode.collaboration.kv_store import InMemoryStore
2
+ from anycode.collaboration.message_bus import MessageBus
3
+ from anycode.collaboration.shared_mem import SharedMemory
4
+ from anycode.collaboration.team import Team
5
+
6
+ __all__ = ["InMemoryStore", "MessageBus", "SharedMemory", "Team"]
@@ -0,0 +1,49 @@
1
+ """Async key-value store backed by a dict."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import UTC, datetime
6
+ from typing import Any
7
+
8
+ from anycode.types import MemoryEntry
9
+
10
+
11
+ class InMemoryStore:
12
+ """Async in-memory key-value store implementing the MemoryStore protocol."""
13
+
14
+ def __init__(self) -> None:
15
+ self._data: dict[str, MemoryEntry] = {}
16
+
17
+ async def get(self, key: str) -> MemoryEntry | None:
18
+ return self._data.get(key)
19
+
20
+ async def set(self, key: str, value: str, metadata: dict[str, Any] | None = None) -> None:
21
+ existing = self._data.get(key)
22
+ self._data[key] = MemoryEntry(
23
+ key=key,
24
+ value=value,
25
+ metadata=dict(metadata) if metadata else None,
26
+ created_at=existing.created_at if existing else datetime.now(UTC),
27
+ )
28
+
29
+ async def list(self) -> list[MemoryEntry]:
30
+ return list(self._data.values())
31
+
32
+ async def delete(self, key: str) -> None:
33
+ self._data.pop(key, None)
34
+
35
+ async def clear(self) -> None:
36
+ self._data.clear()
37
+
38
+ async def search(self, query: str) -> list[MemoryEntry]:
39
+ if not query:
40
+ return await self.list()
41
+ lower = query.lower()
42
+ return [e for e in self._data.values() if lower in e.key.lower() or lower in e.value.lower()]
43
+
44
+ @property
45
+ def size(self) -> int:
46
+ return len(self._data)
47
+
48
+ def has(self, key: str) -> bool:
49
+ return key in self._data
@@ -0,0 +1,70 @@
1
+ """Agent-to-agent message bus with pub/sub and read-state tracking."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from datetime import UTC, datetime
7
+ from uuid import uuid4
8
+
9
+ from anycode.types import Message
10
+
11
+
12
+ class MessageBus:
13
+ """Pub/sub messaging between agents."""
14
+
15
+ def __init__(self) -> None:
16
+ self._messages: list[Message] = []
17
+ self._read_state: dict[str, set[str]] = {}
18
+ self._subscribers: dict[str, dict[int, Callable[[Message], None]]] = {}
19
+ self._next_id = 0
20
+
21
+ def send(self, from_agent: str, to_agent: str, content: str) -> Message:
22
+ msg = Message(id=str(uuid4()), from_agent=from_agent, to_agent=to_agent, content=content, timestamp=datetime.now(UTC))
23
+ self._dispatch(msg)
24
+ return msg
25
+
26
+ def broadcast(self, from_agent: str, content: str) -> Message:
27
+ return self.send(from_agent, "*", content)
28
+
29
+ def get_unread(self, agent_name: str) -> list[Message]:
30
+ read = self._read_state.get(agent_name, set())
31
+ return [m for m in self._messages if _is_recipient(m, agent_name) and m.id not in read]
32
+
33
+ def get_all(self, agent_name: str) -> list[Message]:
34
+ return [m for m in self._messages if _is_recipient(m, agent_name)]
35
+
36
+ def mark_read(self, agent_name: str, message_ids: list[str]) -> None:
37
+ if not message_ids:
38
+ return
39
+ read = self._read_state.setdefault(agent_name, set())
40
+ read.update(message_ids)
41
+
42
+ def get_conversation(self, agent1: str, agent2: str) -> list[Message]:
43
+ return [
44
+ m for m in self._messages
45
+ if (m.from_agent == agent1 and m.to_agent == agent2) or (m.from_agent == agent2 and m.to_agent == agent1)
46
+ ]
47
+
48
+ def subscribe(self, agent_name: str, callback: Callable[[Message], None]) -> Callable[[], None]:
49
+ subs = self._subscribers.setdefault(agent_name, {})
50
+ sub_id = self._next_id
51
+ self._next_id += 1
52
+ subs[sub_id] = callback
53
+ return lambda: subs.pop(sub_id, None)
54
+
55
+ def _dispatch(self, message: Message) -> None:
56
+ self._messages.append(message)
57
+ if message.to_agent == "*":
58
+ for agent_name, subs in self._subscribers.items():
59
+ if agent_name != message.from_agent:
60
+ for cb in subs.values():
61
+ cb(message)
62
+ else:
63
+ for cb in self._subscribers.get(message.to_agent, {}).values():
64
+ cb(message)
65
+
66
+
67
+ def _is_recipient(message: Message, agent_name: str) -> bool:
68
+ if message.to_agent == "*":
69
+ return message.from_agent != agent_name
70
+ return message.to_agent == agent_name
@@ -0,0 +1,55 @@
1
+ """Namespaced shared memory for coordinated agent teams."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from anycode.collaboration.kv_store import InMemoryStore
8
+ from anycode.types import MemoryEntry, MemoryStore
9
+
10
+
11
+ class SharedMemory:
12
+ """Keys are scoped as <agent_name>/<key> to avoid collisions."""
13
+
14
+ def __init__(self) -> None:
15
+ self._store = InMemoryStore()
16
+
17
+ async def write(self, agent_name: str, key: str, value: str, metadata: dict[str, Any] | None = None) -> None:
18
+ namespaced = f"{agent_name}/{key}"
19
+ await self._store.set(namespaced, value, {**(metadata or {}), "agent": agent_name})
20
+
21
+ async def read(self, key: str) -> MemoryEntry | None:
22
+ return await self._store.get(key)
23
+
24
+ async def list_all(self) -> list[MemoryEntry]:
25
+ return await self._store.list()
26
+
27
+ async def list_by_agent(self, agent_name: str) -> list[MemoryEntry]:
28
+ prefix = f"{agent_name}/"
29
+ return [e for e in await self._store.list() if e.key.startswith(prefix)]
30
+
31
+ async def get_summary(self) -> str:
32
+ """Build a markdown summary grouped by agent."""
33
+ all_entries = await self._store.list()
34
+ if not all_entries:
35
+ return ""
36
+
37
+ by_agent: dict[str, list[tuple[str, str]]] = {}
38
+ for entry in all_entries:
39
+ slash_idx = entry.key.find("/")
40
+ agent = entry.key[:slash_idx] if slash_idx != -1 else "_unknown"
41
+ local_key = entry.key[slash_idx + 1:] if slash_idx != -1 else entry.key
42
+ by_agent.setdefault(agent, []).append((local_key, entry.value))
43
+
44
+ lines = ["## Collective Agent Knowledge", ""]
45
+ for agent, entries in by_agent.items():
46
+ lines.append(f"### {agent}")
47
+ for local_key, value in entries:
48
+ display = f"{value[:177]}\u2026" if len(value) > 180 else value
49
+ lines.append(f"- {local_key}: {display}")
50
+ lines.append("")
51
+
52
+ return "\n".join(lines).rstrip()
53
+
54
+ def get_store(self) -> MemoryStore:
55
+ return self._store # type: ignore[return-value]
@@ -0,0 +1,96 @@
1
+ """Manages a named group of agents with messaging, task queue, and shared memory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from typing import Any
7
+
8
+ from anycode.collaboration.message_bus import MessageBus
9
+ from anycode.collaboration.shared_mem import SharedMemory
10
+ from anycode.tasks.queue import TaskQueue
11
+ from anycode.tasks.task import create_task
12
+ from anycode.types import AgentConfig, MemoryStore, Message, OrchestratorEvent, Task, TaskStatus, TeamConfig
13
+
14
+
15
+ class _EventBus:
16
+ def __init__(self) -> None:
17
+ self._listeners: dict[str, dict[int, Callable[[Any], None]]] = {}
18
+ self._next_id = 0
19
+
20
+ def on(self, event: str, handler: Callable[[Any], None]) -> Callable[[], None]:
21
+ subs = self._listeners.setdefault(event, {})
22
+ sub_id = self._next_id
23
+ self._next_id += 1
24
+ subs[sub_id] = handler
25
+ return lambda: subs.pop(sub_id, None)
26
+
27
+ def emit(self, event: str, data: Any) -> None:
28
+ for handler in self._listeners.get(event, {}).values():
29
+ handler(data)
30
+
31
+
32
+ class Team:
33
+ """Agent team with messaging, task queue, and shared memory."""
34
+
35
+ def __init__(self, config: TeamConfig) -> None:
36
+ self.name = config.name
37
+ self.config = config
38
+ self._agent_map: dict[str, AgentConfig] = {a.name: a for a in config.agents}
39
+ self._bus = MessageBus()
40
+ self._queue = TaskQueue()
41
+ self._memory = SharedMemory() if config.shared_memory else None
42
+ self._events = _EventBus()
43
+
44
+ # Relay queue events
45
+ self._queue.on("task:ready", lambda t: self._events.emit("task:ready", OrchestratorEvent(type="task_start", task=t.id, data=t)))
46
+ self._queue.on("task:complete", lambda t: self._events.emit("task:complete", OrchestratorEvent(type="task_complete", task=t.id, data=t)))
47
+ self._queue.on("task:failed", lambda t: self._events.emit("task:failed", OrchestratorEvent(type="error", task=t.id, data=t)))
48
+ self._queue.on("all:complete", lambda: self._events.emit("all:complete", None))
49
+
50
+ def get_agents(self) -> list[AgentConfig]:
51
+ return list(self._agent_map.values())
52
+
53
+ def get_agent(self, name: str) -> AgentConfig | None:
54
+ return self._agent_map.get(name)
55
+
56
+ def send_message(self, from_agent: str, to_agent: str, content: str) -> None:
57
+ msg = self._bus.send(from_agent, to_agent, content)
58
+ self._events.emit("message", OrchestratorEvent(type="message", agent=from_agent, data=msg))
59
+
60
+ def get_messages(self, agent_name: str) -> list[Message]:
61
+ return self._bus.get_all(agent_name)
62
+
63
+ def broadcast(self, from_agent: str, content: str) -> None:
64
+ msg = self._bus.broadcast(from_agent, content)
65
+ self._events.emit("broadcast", OrchestratorEvent(type="message", agent=from_agent, data=msg))
66
+
67
+ def add_task(self, *, title: str, description: str, status: TaskStatus = "pending",
68
+ assignee: str | None = None, depends_on: list[str] | None = None, result: str | None = None) -> Task:
69
+ created = create_task(title=title, description=description, assignee=assignee, depends_on=depends_on)
70
+ final = created.model_copy(update={"status": status, "result": result}) if status != "pending" else created
71
+ self._queue.add(final)
72
+ return final
73
+
74
+ def get_tasks(self) -> list[Task]:
75
+ return self._queue.list()
76
+
77
+ def get_tasks_by_assignee(self, agent_name: str) -> list[Task]:
78
+ return [t for t in self._queue.list() if t.assignee == agent_name]
79
+
80
+ def update_task(self, task_id: str, **kwargs: Any) -> Task:
81
+ return self._queue.update(task_id, **kwargs)
82
+
83
+ def get_next_task(self, agent_name: str) -> Task | None:
84
+ return self._queue.next(agent_name) or self._queue.next_available()
85
+
86
+ def get_shared_memory(self) -> MemoryStore | None:
87
+ return self._memory.get_store() if self._memory else None
88
+
89
+ def get_shared_memory_instance(self) -> SharedMemory | None:
90
+ return self._memory
91
+
92
+ def on(self, event: str, handler: Callable[[Any], None]) -> Callable[[], None]:
93
+ return self._events.on(event, handler)
94
+
95
+ def emit(self, event: str, data: Any) -> None:
96
+ self._events.emit(event, data)
@@ -0,0 +1,7 @@
1
+ from anycode.core.agent import Agent
2
+ from anycode.core.orchestrator import AnyCode, TaskSpec
3
+ from anycode.core.pool import AgentPool
4
+ from anycode.core.runner import AgentRunner
5
+ from anycode.core.scheduler import Scheduler
6
+
7
+ __all__ = ["Agent", "AgentRunner", "AgentPool", "AnyCode", "TaskSpec", "Scheduler"]
anycode/core/agent.py ADDED
@@ -0,0 +1,141 @@
1
+ """Wraps AgentRunner with persistent conversation history, lifecycle state, and streaming."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import AsyncGenerator
6
+
7
+ from anycode.core.runner import AgentRunner
8
+ from anycode.helpers.usage_tracker import EMPTY_USAGE, merge_usage
9
+ from anycode.providers.adapter import create_adapter
10
+ from anycode.tools.executor import ToolExecutor
11
+ from anycode.tools.registry import ToolRegistry
12
+ from anycode.types import (
13
+ AgentConfig,
14
+ AgentInfo,
15
+ AgentRunResult,
16
+ AgentState,
17
+ LLMMessage,
18
+ RunnerOptions,
19
+ RunResult,
20
+ StreamEvent,
21
+ TextBlock,
22
+ ToolDefinition,
23
+ ToolUseContext,
24
+ )
25
+
26
+
27
+ class Agent:
28
+ """High-level agent with state management, conversation history, and streaming support."""
29
+
30
+ def __init__(self, config: AgentConfig | dict[str, object], tool_registry: ToolRegistry, tool_executor: ToolExecutor) -> None:
31
+ typed_config = AgentConfig.model_validate(config) if isinstance(config, dict) else config
32
+ self.name = typed_config.name
33
+ self.config = typed_config
34
+ self._registry = tool_registry
35
+ self._executor = tool_executor
36
+ self._runner: AgentRunner | None = None
37
+ self._state = AgentState()
38
+ self._history: list[LLMMessage] = []
39
+
40
+ async def _get_runner(self) -> AgentRunner:
41
+ if self._runner is not None:
42
+ return self._runner
43
+
44
+ provider = self.config.provider or "anthropic"
45
+ adapter = await create_adapter(provider)
46
+
47
+ opts = RunnerOptions(
48
+ model=self.config.model,
49
+ system_prompt=self.config.system_prompt,
50
+ max_turns=self.config.max_turns,
51
+ max_tokens=self.config.max_tokens,
52
+ temperature=self.config.temperature,
53
+ allowed_tools=self.config.tools,
54
+ agent_name=self.name,
55
+ agent_role=(self.config.system_prompt or "assistant")[:50],
56
+ )
57
+ self._runner = AgentRunner(adapter, self._registry, self._executor, opts)
58
+ return self._runner
59
+
60
+ async def run(self, prompt: str) -> AgentRunResult:
61
+ """Execute prompt as a standalone conversation (history is ignored)."""
62
+ messages = [LLMMessage(role="user", content=[TextBlock(text=prompt)])]
63
+ return await self._execute_run(messages)
64
+
65
+ async def prompt(self, message: str) -> AgentRunResult:
66
+ """Continue the ongoing conversation with a new user message."""
67
+ user_msg = LLMMessage(role="user", content=[TextBlock(text=message)])
68
+ self._history.append(user_msg)
69
+ result = await self._execute_run(list(self._history))
70
+ self._history.extend(result.messages)
71
+ return result
72
+
73
+ async def stream(self, prompt: str) -> AsyncGenerator[StreamEvent, None]:
74
+ """Stream a standalone conversation response as incremental events."""
75
+ messages = [LLMMessage(role="user", content=[TextBlock(text=prompt)])]
76
+ async for event in self._execute_stream(messages):
77
+ yield event
78
+
79
+ def get_state(self) -> AgentState:
80
+ return self._state.model_copy(deep=True)
81
+
82
+ def get_history(self) -> list[LLMMessage]:
83
+ return list(self._history)
84
+
85
+ def reset(self) -> None:
86
+ self._history.clear()
87
+ self._state = AgentState()
88
+
89
+ def add_tool(self, tool: ToolDefinition) -> None:
90
+ self._registry.register(tool)
91
+
92
+ def remove_tool(self, name: str) -> None:
93
+ self._registry.deregister(name)
94
+
95
+ def get_tools(self) -> list[str]:
96
+ return [t.name for t in self._registry.list()]
97
+
98
+ async def _execute_run(self, messages: list[LLMMessage]) -> AgentRunResult:
99
+ self._state.status = "running"
100
+ try:
101
+ runner = await self._get_runner()
102
+ result = await runner.run(messages, on_message=lambda msg: self._state.messages.append(msg))
103
+ self._state.token_usage = merge_usage(self._state.token_usage, result.token_usage)
104
+ self._state.status = "completed"
105
+ return AgentRunResult(
106
+ success=True,
107
+ output=result.output,
108
+ messages=result.messages,
109
+ token_usage=result.token_usage,
110
+ tool_calls=result.tool_calls,
111
+ )
112
+ except Exception as e:
113
+ self._state.status = "error"
114
+ self._state.error = str(e)
115
+ return AgentRunResult(success=False, output=str(e), messages=[], token_usage=EMPTY_USAGE, tool_calls=[])
116
+
117
+ async def _execute_stream(self, messages: list[LLMMessage]) -> AsyncGenerator[StreamEvent, None]:
118
+ self._state.status = "running"
119
+ try:
120
+ runner = await self._get_runner()
121
+ async for event in runner.stream(messages):
122
+ if event.type == "done" and isinstance(event.data, RunResult):
123
+ self._state.token_usage = merge_usage(self._state.token_usage, event.data.token_usage)
124
+ self._state.status = "completed"
125
+ elif event.type == "error":
126
+ self._state.status = "error"
127
+ self._state.error = str(event.data)
128
+ yield event
129
+ except Exception as e:
130
+ self._state.status = "error"
131
+ self._state.error = str(e)
132
+ yield StreamEvent(type="error", data=e)
133
+
134
+ def build_tool_context(self) -> ToolUseContext:
135
+ return ToolUseContext(
136
+ agent=AgentInfo(
137
+ name=self.name,
138
+ role=(self.config.system_prompt or "assistant")[:60],
139
+ model=self.config.model,
140
+ )
141
+ )