feat: initial bootstrap — structure, task_brief, blackboard, adapter bases, escalation, prompts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
0
adapters/__init__.py
Normal file
0
adapters/__init__.py
Normal file
0
adapters/base/__init__.py
Normal file
0
adapters/base/__init__.py
Normal file
50
adapters/base/llm.py
Normal file
50
adapters/base/llm.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
adapters/base/llm.py
|
||||
Abstract base class for all LLM adapters.
|
||||
"""
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class LLMAdapter(ABC):
|
||||
"""
|
||||
Contract that every LLM provider adapter must fulfil.
|
||||
|
||||
Capability strings
|
||||
------------------
|
||||
"reasoning-heavy" — tasks requiring deep chain-of-thought (e.g. T1, T2)
|
||||
"capable" — general-purpose capable model (e.g. T3, T4)
|
||||
"fast-cheap" — high-volume, low-latency tasks (e.g. T5 quick checks)
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def complete(self, prompt: str, capability: str, context: dict) -> str:
|
||||
"""
|
||||
Send a prompt to the model and return the text response.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
prompt : The full prompt string (system + user combined or just user).
|
||||
capability : One of "reasoning-heavy" | "capable" | "fast-cheap".
|
||||
context : Arbitrary key/value bag passed to the adapter (e.g. system
|
||||
prompt override, temperature, max_tokens).
|
||||
|
||||
Returns
|
||||
-------
|
||||
The model's text completion as a plain string.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def resolve_model(self, capability: str) -> str:
|
||||
"""
|
||||
Map a capability string to the concrete model identifier for this provider.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
capability : One of "reasoning-heavy" | "capable" | "fast-cheap".
|
||||
|
||||
Returns
|
||||
-------
|
||||
Model identifier string (e.g. "claude-opus-4-6", "gpt-4o").
|
||||
"""
|
||||
...
|
||||
21
adapters/base/notify.py
Normal file
21
adapters/base/notify.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
adapters/base/notify.py
|
||||
Abstract base class for all notification adapters.
|
||||
"""
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class NotifyAdapter(ABC):
|
||||
"""Contract that every notification provider adapter must fulfil."""
|
||||
|
||||
@abstractmethod
|
||||
def send(self, message: str, context: dict) -> None:
|
||||
"""
|
||||
Dispatch a notification.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
message : Human-readable message text.
|
||||
context : Arbitrary metadata (e.g. channel, severity, run_id, brief_id).
|
||||
"""
|
||||
...
|
||||
64
adapters/base/runtime.py
Normal file
64
adapters/base/runtime.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
adapters/base/runtime.py
|
||||
Abstract base class for all agent-runtime adapters.
|
||||
"""
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class RuntimeAdapter(ABC):
|
||||
"""
|
||||
Contract that every agent runtime adapter must fulfil.
|
||||
|
||||
A "runtime" is responsible for dispatching a task to an actual agent
|
||||
(e.g. an OpenClaw worker, a Claude Code sub-agent, a local subprocess)
|
||||
and retrieving its result.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def spawn(self, task: str, capability: str, context: dict) -> str:
|
||||
"""
|
||||
Start an agent to work on the given task.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
task : Natural-language description of the work to perform.
|
||||
capability : Capability hint — "reasoning-heavy" | "capable" | "fast-cheap".
|
||||
context : Arbitrary key/value bag (e.g. files, constraints, brief payload).
|
||||
|
||||
Returns
|
||||
-------
|
||||
A provider-specific agent_id string that can be used to poll for results.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def get_result(self, agent_id: str, timeout_s: int) -> dict:
|
||||
"""
|
||||
Block until the agent completes or the timeout elapses.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
agent_id : The id returned by spawn().
|
||||
timeout_s : Maximum seconds to wait before raising TimeoutError.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A dict containing at minimum:
|
||||
{
|
||||
"status": "done" | "failed" | "partial" | "blocked",
|
||||
"output": <str or dict>,
|
||||
"artifacts": [...], # optional
|
||||
}
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def kill(self, agent_id: str) -> None:
|
||||
"""
|
||||
Terminate a running agent unconditionally.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
agent_id : The id returned by spawn().
|
||||
"""
|
||||
...
|
||||
69
adapters/base/vcs.py
Normal file
69
adapters/base/vcs.py
Normal file
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
adapters/base/vcs.py
|
||||
Abstract base class for all Version Control System adapters.
|
||||
"""
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class VCSAdapter(ABC):
|
||||
"""Contract that every VCS provider adapter must fulfil."""
|
||||
|
||||
@abstractmethod
|
||||
def create_branch(self, name: str) -> None:
|
||||
"""
|
||||
Create a new branch with the given name.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : Branch name (e.g. "feat/webhook-ingestion").
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def commit(self, files: list[str], message: str) -> str:
|
||||
"""
|
||||
Stage the given files and create a commit.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
files : List of file paths to stage (relative to repo root).
|
||||
message : Commit message.
|
||||
|
||||
Returns
|
||||
-------
|
||||
The commit SHA as a string.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def create_pr(self, title: str, body: str, head: str, base: str) -> str:
|
||||
"""
|
||||
Open a pull request.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
title : PR title.
|
||||
body : PR description / body markdown.
|
||||
head : Head branch name.
|
||||
base : Base branch name (e.g. "main").
|
||||
|
||||
Returns
|
||||
-------
|
||||
The URL of the created pull request.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def get_pr_status(self, pr_id: str) -> str:
|
||||
"""
|
||||
Fetch the current status of a pull request.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pr_id : Provider-specific PR identifier (number, node-id, or URL).
|
||||
|
||||
Returns
|
||||
-------
|
||||
One of: "open" | "merged" | "closed".
|
||||
"""
|
||||
...
|
||||
0
adapters/llm/__init__.py
Normal file
0
adapters/llm/__init__.py
Normal file
44
adapters/llm/anthropic.py
Normal file
44
adapters/llm/anthropic.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
adapters/llm/anthropic.py
|
||||
Anthropic Claude adapter — Phase 2 stub.
|
||||
|
||||
TODO (Phase 2):
|
||||
- Implement complete() using the anthropic SDK (anthropic.Anthropic client).
|
||||
- Implement resolve_model() by reading config/team.yaml capability_map.
|
||||
- Handle streaming responses, rate-limit retries, and token counting.
|
||||
- Support system-prompt injection via context["system_prompt"].
|
||||
- Map capability → model using the provider's capability_map config.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from adapters.base.llm import LLMAdapter
|
||||
|
||||
|
||||
class AnthropicAdapter(LLMAdapter):
|
||||
"""
|
||||
LLM adapter for Anthropic Claude models.
|
||||
|
||||
Reads model configuration from config/team.yaml:
|
||||
models.provider: anthropic
|
||||
models.capability_map.reasoning-heavy.anthropic: claude-opus-4-6
|
||||
models.capability_map.capable.anthropic: claude-sonnet-4-6
|
||||
models.capability_map.fast-cheap.anthropic: claude-haiku-3-5
|
||||
"""
|
||||
|
||||
def __init__(self, config: dict) -> None:
|
||||
# TODO (Phase 2): Accept loaded team.yaml config dict.
|
||||
# Extract API key from environment (ANTHROPIC_API_KEY).
|
||||
# Initialise the anthropic.Anthropic() client.
|
||||
raise NotImplementedError("AnthropicAdapter.__init__ is not yet implemented.")
|
||||
|
||||
def complete(self, prompt: str, capability: str, context: dict) -> str:
|
||||
# TODO (Phase 2): Call anthropic client messages.create().
|
||||
# Use resolve_model(capability) to pick the model.
|
||||
# Support context keys: system_prompt, max_tokens, temperature.
|
||||
# Return response text as a plain string.
|
||||
raise NotImplementedError("AnthropicAdapter.complete is not yet implemented.")
|
||||
|
||||
def resolve_model(self, capability: str) -> str:
|
||||
# TODO (Phase 2): Look up capability in team.yaml capability_map.
|
||||
# Fall back to "capable" tier model if capability is unknown.
|
||||
raise NotImplementedError("AnthropicAdapter.resolve_model is not yet implemented.")
|
||||
0
adapters/notify/__init__.py
Normal file
0
adapters/notify/__init__.py
Normal file
35
adapters/notify/openclaw.py
Normal file
35
adapters/notify/openclaw.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""
|
||||
adapters/notify/openclaw.py
|
||||
OpenClaw notification adapter — Phase 2 stub.
|
||||
|
||||
TODO (Phase 2):
|
||||
- Implement send() to dispatch notifications via the OpenClaw API.
|
||||
- Support context keys: channel, severity, run_id, brief_id.
|
||||
- Read endpoint and credentials from environment (OPENCLAW_API_KEY, OPENCLAW_URL).
|
||||
- Handle rate limiting and delivery retries.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from adapters.base.notify import NotifyAdapter
|
||||
|
||||
|
||||
class OpenClawNotifyAdapter(NotifyAdapter):
|
||||
"""
|
||||
Notification adapter that sends messages via OpenClaw.
|
||||
|
||||
Expects environment variables:
|
||||
OPENCLAW_API_KEY — authentication token
|
||||
OPENCLAW_URL — base URL for the OpenClaw API (optional, defaults to hosted)
|
||||
"""
|
||||
|
||||
def __init__(self, config: dict) -> None:
|
||||
# TODO (Phase 2): Accept loaded team.yaml config dict.
|
||||
# Extract OPENCLAW_API_KEY and OPENCLAW_URL from environment.
|
||||
# Initialise an HTTP client (e.g. httpx or requests).
|
||||
raise NotImplementedError("OpenClawNotifyAdapter.__init__ is not yet implemented.")
|
||||
|
||||
def send(self, message: str, context: dict) -> None:
|
||||
# TODO (Phase 2): POST notification payload to OpenClaw API.
|
||||
# Include message, context (channel, severity, run_id, brief_id).
|
||||
# Log delivery confirmation or raise on failure.
|
||||
raise NotImplementedError("OpenClawNotifyAdapter.send is not yet implemented.")
|
||||
0
adapters/runtime/__init__.py
Normal file
0
adapters/runtime/__init__.py
Normal file
51
adapters/runtime/claude_code.py
Normal file
51
adapters/runtime/claude_code.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
adapters/runtime/claude_code.py
|
||||
Claude Code agent runtime adapter — Phase 2 stub.
|
||||
|
||||
TODO (Phase 2):
|
||||
- Implement spawn() to launch a Claude Code sub-agent via the Agent SDK.
|
||||
- Implement get_result() to await agent completion and parse the output.
|
||||
- Implement kill() to terminate the sub-agent process or session.
|
||||
- Map task brief context (files, constraints, artifacts) into the agent's
|
||||
system prompt and tool context.
|
||||
- Handle Claude Code tool-use responses and extract structured output.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from adapters.base.runtime import RuntimeAdapter
|
||||
|
||||
|
||||
class ClaudeCodeRuntimeAdapter(RuntimeAdapter):
|
||||
"""
|
||||
Runtime adapter that spawns Claude Code sub-agents for coding tasks.
|
||||
|
||||
Used when a TaskBrief has preferred_runtime == "coding_agent".
|
||||
|
||||
Expects the Claude Code CLI / Agent SDK to be available in the environment.
|
||||
Credentials are inherited from the environment (ANTHROPIC_API_KEY).
|
||||
"""
|
||||
|
||||
def __init__(self, config: dict) -> None:
|
||||
# TODO (Phase 2): Accept loaded team.yaml config dict.
|
||||
# Validate that Claude Code CLI or SDK is accessible.
|
||||
# Initialise any agent session management state.
|
||||
raise NotImplementedError("ClaudeCodeRuntimeAdapter.__init__ is not yet implemented.")
|
||||
|
||||
def spawn(self, task: str, capability: str, context: dict) -> str:
|
||||
# TODO (Phase 2): Launch a Claude Code sub-agent.
|
||||
# Compose a structured system prompt from task + context.
|
||||
# Inject relevant files and constraints as tool context.
|
||||
# Return an agent_id that maps to a running agent session.
|
||||
raise NotImplementedError("ClaudeCodeRuntimeAdapter.spawn is not yet implemented.")
|
||||
|
||||
def get_result(self, agent_id: str, timeout_s: int) -> dict:
|
||||
# TODO (Phase 2): Await the Claude Code agent session to complete.
|
||||
# Parse the agent's final message for structured JSON output.
|
||||
# Return dict with: {"status": ..., "output": ..., "artifacts": [...]}.
|
||||
# Raise TimeoutError if timeout_s elapses.
|
||||
raise NotImplementedError("ClaudeCodeRuntimeAdapter.get_result is not yet implemented.")
|
||||
|
||||
def kill(self, agent_id: str) -> None:
|
||||
# TODO (Phase 2): Terminate the Claude Code agent session.
|
||||
# Clean up any temporary files or session state.
|
||||
raise NotImplementedError("ClaudeCodeRuntimeAdapter.kill is not yet implemented.")
|
||||
48
adapters/runtime/openclaw.py
Normal file
48
adapters/runtime/openclaw.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
adapters/runtime/openclaw.py
|
||||
OpenClaw agent runtime adapter — Phase 2 stub.
|
||||
|
||||
TODO (Phase 2):
|
||||
- Implement spawn() to submit a task to an OpenClaw worker pool.
|
||||
- Implement get_result() to poll or subscribe for agent completion.
|
||||
- Implement kill() to cancel a running OpenClaw agent job.
|
||||
- Read endpoint and credentials from environment (OPENCLAW_API_KEY, OPENCLAW_URL).
|
||||
- Map capability hint to an appropriate worker class/queue.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from adapters.base.runtime import RuntimeAdapter
|
||||
|
||||
|
||||
class OpenClawRuntimeAdapter(RuntimeAdapter):
|
||||
"""
|
||||
Runtime adapter that dispatches agent tasks to OpenClaw workers.
|
||||
|
||||
Expects environment variables:
|
||||
OPENCLAW_API_KEY — authentication token
|
||||
OPENCLAW_URL — base URL for the OpenClaw API
|
||||
"""
|
||||
|
||||
def __init__(self, config: dict) -> None:
|
||||
# TODO (Phase 2): Accept loaded team.yaml config dict.
|
||||
# Extract OPENCLAW_API_KEY and OPENCLAW_URL from environment.
|
||||
# Initialise HTTP client and any job-tracking state.
|
||||
raise NotImplementedError("OpenClawRuntimeAdapter.__init__ is not yet implemented.")
|
||||
|
||||
def spawn(self, task: str, capability: str, context: dict) -> str:
|
||||
# TODO (Phase 2): Submit task to OpenClaw worker pool.
|
||||
# Map capability ("reasoning-heavy" | "capable" | "fast-cheap") to
|
||||
# an appropriate worker queue or model hint.
|
||||
# Return an agent_id string that can be used to poll for results.
|
||||
raise NotImplementedError("OpenClawRuntimeAdapter.spawn is not yet implemented.")
|
||||
|
||||
def get_result(self, agent_id: str, timeout_s: int) -> dict:
|
||||
# TODO (Phase 2): Poll or long-poll the OpenClaw API for job completion.
|
||||
# Raise TimeoutError if timeout_s elapses before the job finishes.
|
||||
# Return a dict with at minimum: {"status": ..., "output": ..., "artifacts": [...]}.
|
||||
raise NotImplementedError("OpenClawRuntimeAdapter.get_result is not yet implemented.")
|
||||
|
||||
def kill(self, agent_id: str) -> None:
|
||||
# TODO (Phase 2): Send a cancellation request to the OpenClaw API.
|
||||
# Silently succeed if the agent has already finished.
|
||||
raise NotImplementedError("OpenClawRuntimeAdapter.kill is not yet implemented.")
|
||||
0
adapters/vcs/__init__.py
Normal file
0
adapters/vcs/__init__.py
Normal file
51
adapters/vcs/github.py
Normal file
51
adapters/vcs/github.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
adapters/vcs/github.py
|
||||
GitHub VCS adapter — Phase 2 stub.
|
||||
|
||||
TODO (Phase 2):
|
||||
- Implement create_branch() using PyGithub or gh CLI subprocess.
|
||||
- Implement commit() — stage files and push via git subprocess or API.
|
||||
- Implement create_pr() using GitHub REST API (POST /repos/{owner}/{repo}/pulls).
|
||||
- Implement get_pr_status() using GET /repos/{owner}/{repo}/pulls/{pull_number}.
|
||||
- Read repo and credentials from config/team.yaml and environment (GITHUB_TOKEN).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from adapters.base.vcs import VCSAdapter
|
||||
|
||||
|
||||
class GitHubAdapter(VCSAdapter):
|
||||
"""
|
||||
VCS adapter for GitHub repositories.
|
||||
|
||||
Expects environment variable GITHUB_TOKEN and config values:
|
||||
run.repo — SSH or HTTPS clone URL
|
||||
run.base_branch — default base branch (e.g. "main")
|
||||
"""
|
||||
|
||||
def __init__(self, config: dict) -> None:
|
||||
# TODO (Phase 2): Accept loaded team.yaml config dict.
|
||||
# Extract GITHUB_TOKEN from environment.
|
||||
# Parse owner/repo from config.run.repo.
|
||||
raise NotImplementedError("GitHubAdapter.__init__ is not yet implemented.")
|
||||
|
||||
def create_branch(self, name: str) -> None:
|
||||
# TODO (Phase 2): Create branch via GitHub API or local git subprocess.
|
||||
# Use config.run.base_branch as the branch point.
|
||||
raise NotImplementedError("GitHubAdapter.create_branch is not yet implemented.")
|
||||
|
||||
def commit(self, files: list[str], message: str) -> str:
|
||||
# TODO (Phase 2): Stage files (git add), create commit (git commit), push.
|
||||
# Return the resulting commit SHA.
|
||||
raise NotImplementedError("GitHubAdapter.commit is not yet implemented.")
|
||||
|
||||
def create_pr(self, title: str, body: str, head: str, base: str) -> str:
|
||||
# TODO (Phase 2): POST to GitHub API /repos/{owner}/{repo}/pulls.
|
||||
# Return the HTML URL of the created PR.
|
||||
raise NotImplementedError("GitHubAdapter.create_pr is not yet implemented.")
|
||||
|
||||
def get_pr_status(self, pr_id: str) -> str:
|
||||
# TODO (Phase 2): GET /repos/{owner}/{repo}/pulls/{number}.
|
||||
# Map GitHub PR state ("open", "closed") + merged flag to
|
||||
# our schema: "open" | "merged" | "closed".
|
||||
raise NotImplementedError("GitHubAdapter.get_pr_status is not yet implemented.")
|
||||
Reference in New Issue
Block a user