Initial commit
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
"""Prompt harness helpers for Native Runtime V2."""
|
||||
|
||||
from .artifacts import (
|
||||
RUNTIME_ARTIFACT_DELTA_HEADER,
|
||||
RUNTIME_ARTIFACT_HEADER,
|
||||
is_runtime_artifact_message,
|
||||
render_runtime_artifact_messages,
|
||||
strip_runtime_artifact_messages,
|
||||
)
|
||||
from .builder import PromptHarnessBuilder
|
||||
from .tool_strategy import NativeToolStrategyBuilder
|
||||
from .types import PromptHarnessOutput, RuntimeArtifact
|
||||
|
||||
__all__ = [
|
||||
"PromptHarnessBuilder",
|
||||
"NativeToolStrategyBuilder",
|
||||
"PromptHarnessOutput",
|
||||
"RuntimeArtifact",
|
||||
"RUNTIME_ARTIFACT_HEADER",
|
||||
"RUNTIME_ARTIFACT_DELTA_HEADER",
|
||||
"is_runtime_artifact_message",
|
||||
"render_runtime_artifact_messages",
|
||||
"strip_runtime_artifact_messages",
|
||||
]
|
||||
@@ -0,0 +1,83 @@
|
||||
"""Runtime artifact rendering helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from typing import Any, Iterable
|
||||
|
||||
from .deltas import changed_artifacts
|
||||
from .types import RuntimeArtifact
|
||||
|
||||
|
||||
RUNTIME_ARTIFACT_HEADER = "## Runtime Artifact:"
|
||||
RUNTIME_ARTIFACT_DELTA_HEADER = "## Runtime Artifact Delta:"
|
||||
|
||||
|
||||
def artifact_content_hash(content: str, metadata: dict[str, Any] | None = None) -> str:
|
||||
raw = json.dumps(
|
||||
{
|
||||
"content": str(content or ""),
|
||||
"metadata": dict(metadata or {}),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
default=str,
|
||||
)
|
||||
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def build_runtime_artifact_record(
|
||||
artifact_type: str,
|
||||
title: str,
|
||||
content: str,
|
||||
*,
|
||||
scope: str = "runtime",
|
||||
metadata: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
content_hash = artifact_content_hash(content, metadata)
|
||||
return RuntimeArtifact(
|
||||
artifact_type=artifact_type,
|
||||
title=title,
|
||||
content=str(content or "").strip(),
|
||||
scope=scope,
|
||||
metadata={"content_hash": content_hash, **dict(metadata or {})},
|
||||
).to_record(content_hash=content_hash)
|
||||
|
||||
|
||||
def build_runtime_artifact_manifest(artifacts: Iterable[RuntimeArtifact]) -> tuple[list[dict[str, Any]], dict[str, str]]:
|
||||
manifest: list[dict[str, Any]] = []
|
||||
hashes: dict[str, str] = {}
|
||||
for artifact in artifacts:
|
||||
content_hash = artifact_content_hash(artifact.content, artifact.metadata)
|
||||
record = artifact.to_record(content_hash=content_hash)
|
||||
manifest.append(record)
|
||||
hashes[artifact.artifact_type] = content_hash
|
||||
return manifest, hashes
|
||||
|
||||
|
||||
def render_runtime_artifact_messages(
|
||||
artifacts: Iterable[RuntimeArtifact],
|
||||
*,
|
||||
previous_hashes: dict[str, str] | None = None,
|
||||
emit_delta_messages: bool = True,
|
||||
) -> list[dict[str, str]]:
|
||||
messages: list[dict[str, str]] = []
|
||||
for artifact, is_delta in changed_artifacts(artifacts, previous_hashes):
|
||||
header = RUNTIME_ARTIFACT_DELTA_HEADER if emit_delta_messages and is_delta else RUNTIME_ARTIFACT_HEADER
|
||||
messages.append({
|
||||
"role": "system",
|
||||
"content": f"{header} {artifact.title}\n{artifact.content}".strip(),
|
||||
})
|
||||
return messages
|
||||
|
||||
|
||||
def is_runtime_artifact_message(message: dict[str, Any]) -> bool:
|
||||
if str(message.get("role", "") or "") != "system":
|
||||
return False
|
||||
content = str(message.get("content", "") or "")
|
||||
return content.startswith(RUNTIME_ARTIFACT_HEADER) or content.startswith(RUNTIME_ARTIFACT_DELTA_HEADER)
|
||||
|
||||
|
||||
def strip_runtime_artifact_messages(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
return [message for message in messages if not is_runtime_artifact_message(message)]
|
||||
@@ -0,0 +1,182 @@
|
||||
"""Prompt harness builder for NativeAgent."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from opc.layer2_organization.prompt_contract import is_report_prompt_turn
|
||||
from opc.layer2_organization.session_scoping import is_top_level_company_session
|
||||
|
||||
from .artifacts import build_runtime_artifact_manifest, render_runtime_artifact_messages
|
||||
from .tool_strategy import NativeToolStrategyBuilder
|
||||
from .types import PromptHarnessOutput, RuntimeArtifact
|
||||
|
||||
|
||||
def _final_decider_role_id(task: Any) -> str:
|
||||
metadata = dict(getattr(task, "metadata", {}) or {})
|
||||
final_role = str(metadata.get("final_decider_role_id", "") or "").strip()
|
||||
if final_role:
|
||||
return final_role
|
||||
top_level = [str(item).strip() for item in list(metadata.get("top_level_role_ids", []) or []) if str(item).strip()]
|
||||
if len(top_level) == 1:
|
||||
return top_level[0]
|
||||
return ""
|
||||
|
||||
|
||||
def _memory_skill_user_facing(task: Any, role_id: str) -> bool:
|
||||
metadata = dict(getattr(task, "metadata", {}) or {})
|
||||
execution_mode = str(metadata.get("execution_mode", "") or "").strip()
|
||||
if execution_mode != "company_mode":
|
||||
return True
|
||||
if is_report_prompt_turn(metadata):
|
||||
return False
|
||||
current_role = str(role_id or getattr(task, "assigned_to", "") or metadata.get("work_item_role_id", "") or "").strip()
|
||||
if not current_role or current_role != _final_decider_role_id(task):
|
||||
return False
|
||||
return bool(metadata.get("user_visible", False) or is_top_level_company_session(task))
|
||||
|
||||
|
||||
def _execution_mode(task: Any) -> str | None:
|
||||
return str(getattr(task, "metadata", {}).get("execution_mode", "") or "").strip() or None
|
||||
|
||||
|
||||
def _resume_content(runtime_resume: dict[str, Any]) -> str:
|
||||
lines = [
|
||||
f"- Runtime session: {str(runtime_resume.get('runtime_session_id', '') or '').strip()}",
|
||||
f"- Resume cursor: {runtime_resume.get('resume_cursor', '')}",
|
||||
f"- Active subagents: {len(runtime_resume.get('active_subagents', []) or [])}",
|
||||
f"- Permission requests: {len(runtime_resume.get('permission_requests', []) or [])}",
|
||||
f"- Task ledger items: {len(runtime_resume.get('task_ledger', []) or [])}",
|
||||
]
|
||||
worktree_path = str(runtime_resume.get("worktree_path", "") or "").strip()
|
||||
if worktree_path:
|
||||
lines.append(f"- Worktree path: {worktree_path}")
|
||||
verification_verdict = str(runtime_resume.get("verification_verdict", "") or "").strip()
|
||||
if verification_verdict:
|
||||
lines.append(f"- Last verification: {verification_verdict}")
|
||||
return "Resume envelope:\n" + "\n".join(lines)
|
||||
|
||||
|
||||
def _runtime_resume_payload(task: Any) -> dict[str, Any]:
|
||||
context_snapshot = getattr(task, "context_snapshot", {}) or {}
|
||||
if not isinstance(context_snapshot, dict):
|
||||
return {}
|
||||
raw_resume = context_snapshot.get("runtime_resume", {})
|
||||
return dict(raw_resume) if isinstance(raw_resume, dict) else {}
|
||||
|
||||
|
||||
class PromptHarnessBuilder:
|
||||
"""Build dynamic sections and boot artifact messages for NativeAgent."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
task: Any,
|
||||
role_id: str,
|
||||
config: Any,
|
||||
context_assembler: Any,
|
||||
preferences: Any,
|
||||
skills: Any,
|
||||
) -> None:
|
||||
self.task = task
|
||||
self.role_id = role_id
|
||||
self.config = config
|
||||
self.context_assembler = context_assembler
|
||||
self.preferences = preferences
|
||||
self.skills = skills
|
||||
|
||||
async def build(
|
||||
self,
|
||||
*,
|
||||
system_prompt: str,
|
||||
allowed_tools: list[str] | None = None,
|
||||
runtime_policy_messages: list[dict[str, Any]] | None = None,
|
||||
) -> PromptHarnessOutput:
|
||||
runtime_policy_messages = list(runtime_policy_messages or [])
|
||||
cfg = self.config.system.native_runtime.prompt_harness
|
||||
if not cfg.enabled:
|
||||
return PromptHarnessOutput(
|
||||
system_prompt=system_prompt,
|
||||
runtime_policy_messages=runtime_policy_messages,
|
||||
)
|
||||
|
||||
workspace_context_messages: list[dict[str, str]] = []
|
||||
dynamic_section_ids: list[str] = []
|
||||
if cfg.split_static_dynamic:
|
||||
assembled_ctx = await self.context_assembler.build_system_context(self.task, role_id=self.role_id)
|
||||
if assembled_ctx:
|
||||
workspace_context_messages.append({"role": "system", "content": assembled_ctx})
|
||||
dynamic_section_ids.append("assembled_context")
|
||||
# employee_delta_context is already rendered inside the
|
||||
# unified Self section produced by
|
||||
# ``ContextAssembler._build_self_section`` (which is
|
||||
# included in ``assembled_ctx`` above). Emitting it a
|
||||
# second time here would duplicate the delta profile in
|
||||
# the prompt, so we rely solely on the assembled context.
|
||||
|
||||
artifacts: list[RuntimeArtifact] = []
|
||||
if cfg.artifact_messages_enabled:
|
||||
execution_mode = _execution_mode(self.task)
|
||||
tool_surface = NativeToolStrategyBuilder(
|
||||
list(allowed_tools or []),
|
||||
company_mode=execution_mode == "company_mode",
|
||||
).render()
|
||||
artifacts.append(RuntimeArtifact(
|
||||
artifact_type="tool_surface_delta",
|
||||
title="Tool Strategy",
|
||||
content=tool_surface,
|
||||
metadata={"allowed_tools": sorted(list(allowed_tools or []))},
|
||||
))
|
||||
skills_summary = str(
|
||||
self.skills.build_skills_summary(
|
||||
self.task.project_id,
|
||||
execution_mode=execution_mode,
|
||||
role_id=self.role_id,
|
||||
user_facing=_memory_skill_user_facing(self.task, self.role_id),
|
||||
final_decider_role_id=_final_decider_role_id(self.task),
|
||||
)
|
||||
or ""
|
||||
).strip()
|
||||
if skills_summary:
|
||||
artifacts.append(RuntimeArtifact(
|
||||
artifact_type="skills_delta",
|
||||
title="Skills",
|
||||
content=skills_summary,
|
||||
metadata={"project_id": self.task.project_id or "default"},
|
||||
))
|
||||
resident_assignment = dict(self.task.context_snapshot.get("resident_assignment", {}) or self.task.metadata.get("resident_assignment", {}) or {})
|
||||
team_memory_digest = str(resident_assignment.get("team_memory_digest", "") or "").strip()
|
||||
if team_memory_digest:
|
||||
artifacts.append(RuntimeArtifact(
|
||||
artifact_type="team_memory_delta",
|
||||
title="Team Memory",
|
||||
content=team_memory_digest,
|
||||
metadata={"assignment_id": resident_assignment.get("assignment_id", "")},
|
||||
))
|
||||
runtime_resume = _runtime_resume_payload(self.task)
|
||||
if runtime_resume:
|
||||
artifacts.append(RuntimeArtifact(
|
||||
artifact_type="resume_state",
|
||||
title="Resume State",
|
||||
content=_resume_content(runtime_resume),
|
||||
metadata={"runtime_session_id": runtime_resume.get("runtime_session_id", "")},
|
||||
))
|
||||
|
||||
previous_hashes = dict((self.task.metadata.get("prompt_harness", {}) or {}).get("artifact_hashes", {}) or {})
|
||||
artifact_messages = render_runtime_artifact_messages(
|
||||
artifacts,
|
||||
previous_hashes=previous_hashes,
|
||||
emit_delta_messages=cfg.emit_delta_messages,
|
||||
)
|
||||
artifact_manifest, artifact_hashes = build_runtime_artifact_manifest(artifacts)
|
||||
return PromptHarnessOutput(
|
||||
system_prompt=system_prompt,
|
||||
runtime_policy_messages=runtime_policy_messages,
|
||||
workspace_context_messages=workspace_context_messages,
|
||||
dynamic_messages=workspace_context_messages,
|
||||
artifact_messages=artifact_messages,
|
||||
static_section_ids=["system_prompt"],
|
||||
dynamic_section_ids=dynamic_section_ids,
|
||||
artifact_manifest=artifact_manifest,
|
||||
artifact_hashes=artifact_hashes,
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
"""Artifact delta helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Iterable
|
||||
|
||||
from .types import RuntimeArtifact
|
||||
|
||||
|
||||
def changed_artifacts(
|
||||
artifacts: Iterable[RuntimeArtifact],
|
||||
previous_hashes: dict[str, str] | None = None,
|
||||
) -> list[tuple[RuntimeArtifact, bool]]:
|
||||
previous = dict(previous_hashes or {})
|
||||
changed: list[tuple[RuntimeArtifact, bool]] = []
|
||||
for artifact in artifacts:
|
||||
content_hash = str(artifact.metadata.get("content_hash", "") or "")
|
||||
is_delta = bool(previous.get(artifact.artifact_type)) and previous.get(artifact.artifact_type) != content_hash
|
||||
changed.append((artifact, is_delta))
|
||||
return changed
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Static prompt harness sections."""
|
||||
|
||||
DEDICATED_TOOL_DISCIPLINE = """
|
||||
## Dedicated Tool Discipline
|
||||
- Prefer dedicated file/search/browser tools over shell commands when both can accomplish the same task.
|
||||
- Use shell execution for commands, builds, tests, and process control. Do not use it as a substitute for file reading or editing when dedicated tools exist.
|
||||
- If a dedicated tool fails for environmental reasons, explain that briefly and then fall back to the next-best tool.
|
||||
"""
|
||||
|
||||
SAFE_ACTIONS_CONTRACT = """
|
||||
## Safe Actions Contract
|
||||
- Reversible local actions such as reading files, editing code in the workspace, and running tests are normally acceptable.
|
||||
- Destructive or shared-state actions require a higher bar: deleting data, force-pushing, changing CI/CD, altering database schema, sending outbound messages, or touching infrastructure should trigger approval or an explicit user decision.
|
||||
- Do not use destructive operations to bypass an obstacle. Investigate first, then fix the cause.
|
||||
"""
|
||||
|
||||
HONEST_REPORTING_CONTRACT = """
|
||||
## Honest Reporting Contract
|
||||
- Never claim a command, test, or validation step succeeded unless you actually ran it and saw the output.
|
||||
- Never hide failing checks or rewrite their meaning to sound successful.
|
||||
- If you could not verify something, say that directly and explain why in one sentence.
|
||||
"""
|
||||
|
||||
MEMORY_TRUST_CONTRACT = """
|
||||
## Memory Trust Contract
|
||||
- Memory is guidance, not ground truth.
|
||||
- If memory names a repo fact, file path, behavior, or convention that could have changed, verify it against the current workspace before relying on it.
|
||||
- If current evidence conflicts with memory, trust the current evidence and update the memory later instead of forcing the old assumption.
|
||||
"""
|
||||
|
||||
SUBAGENT_HARNESS_CONTRACT = """
|
||||
## Subagent Harness Contract
|
||||
- Use fresh subagents when you need isolation or a different write scope.
|
||||
- Use fork-style inheritance when the child clearly benefits from the current context and tool surface.
|
||||
- Do not duplicate work already delegated.
|
||||
- When delegating implementation or verification, make the prompt self-contained and explicit about scope, expected output, and constraints.
|
||||
"""
|
||||
|
||||
LONG_RUNNING_SESSION_CONTRACT = """
|
||||
## Long-Running Session Contract
|
||||
- This runtime may summarize history, compact context, and re-inject structured state.
|
||||
- Preserve important state in structured tools and artifacts, not only in assistant prose.
|
||||
- When continuing after a long task, rely on the current runtime state, task ledger, and reinjected artifacts before re-solving old work.
|
||||
"""
|
||||
@@ -0,0 +1,110 @@
|
||||
"""Native runtime tool strategy rendering."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
_FILE_READ_TOOLS = {"file_read", "list_dir", "glob", "grep", "file_search"}
|
||||
_FILE_EDIT_TOOLS = {"file_write", "file_edit", "apply_patch"}
|
||||
_SHELL_TOOLS = {"shell_exec"}
|
||||
_PYTHON_TOOLS = {"python_exec"}
|
||||
_WEB_TOOLS = {"web_search", "web_fetch"}
|
||||
_BROWSER_TOOLS = {
|
||||
"browser_navigate",
|
||||
"browser_navigate_back",
|
||||
"browser_click",
|
||||
"browser_snapshot",
|
||||
"browser_type",
|
||||
"browser_wait_for",
|
||||
"browser_scroll",
|
||||
"browser_select_option",
|
||||
"browser_take_screenshot",
|
||||
"browser_close",
|
||||
}
|
||||
_TODO_TOOLS = {"todo_write", "todo_read"}
|
||||
_SUBAGENT_TOOLS = {"agent_spawn", "agent_wait", "agent_send", "agent_list"}
|
||||
_COMPANY_COLLABORATION_TOOLS = {
|
||||
"inbox",
|
||||
"send_dm",
|
||||
"ask_peer_and_wait",
|
||||
"reply_message",
|
||||
"broadcast_issue",
|
||||
"delegate_work",
|
||||
"modify_work_item",
|
||||
"delete_work_item",
|
||||
"manager_board_read",
|
||||
"manager_board_update",
|
||||
"manager_board_release",
|
||||
"manager_board_rollup",
|
||||
"start_meeting",
|
||||
"respond_meeting",
|
||||
"propose_runtime_replan",
|
||||
"propose_task_adjustment",
|
||||
"route_work",
|
||||
"find_and_ask_expert",
|
||||
"read_inbox",
|
||||
}
|
||||
|
||||
|
||||
class NativeToolStrategyBuilder:
|
||||
"""Render concise tool-selection guidance for the current native tool surface."""
|
||||
|
||||
def __init__(self, allowed_tools: list[str] | None, *, company_mode: bool = False) -> None:
|
||||
self.allowed_tools = {
|
||||
str(item or "").strip()
|
||||
for item in list(allowed_tools or [])
|
||||
if str(item or "").strip()
|
||||
}
|
||||
self.company_mode = bool(company_mode)
|
||||
|
||||
def render(self) -> str:
|
||||
if not self.allowed_tools:
|
||||
return "No explicit tool surface was supplied for this runtime."
|
||||
|
||||
lines = [
|
||||
"Use the current tool schema as the source of truth for exact arguments.",
|
||||
f"Available tool count: {len(self.allowed_tools)}",
|
||||
f"Available tools: {self._preview_tools()}",
|
||||
"",
|
||||
"Selection strategy:",
|
||||
]
|
||||
rules = self._rules()
|
||||
if rules:
|
||||
lines.extend(f"- {rule}" for rule in rules)
|
||||
else:
|
||||
lines.append("- Use the provided tools directly when they advance the task.")
|
||||
return "\n".join(lines).strip()
|
||||
|
||||
def _preview_tools(self) -> str:
|
||||
ordered = sorted(self.allowed_tools)
|
||||
preview = ", ".join(ordered[:24])
|
||||
suffix = "" if len(ordered) <= 24 else f", +{len(ordered) - 24} more"
|
||||
return f"{preview}{suffix}"
|
||||
|
||||
def _rules(self) -> list[str]:
|
||||
rules: list[str] = []
|
||||
tools = self.allowed_tools
|
||||
has_file_read = bool(tools & _FILE_READ_TOOLS)
|
||||
has_file_edit = bool(tools & _FILE_EDIT_TOOLS)
|
||||
if has_file_read:
|
||||
rules.append("Use dedicated read/search/list tools for workspace inspection instead of shell text commands.")
|
||||
if has_file_edit:
|
||||
rules.append("Use dedicated edit/write/patch tools for file changes; verify the resulting diff or file content when useful.")
|
||||
if tools & _SHELL_TOOLS:
|
||||
rules.append("Use shell execution for commands, builds, tests, package scripts, and process control.")
|
||||
if tools & _PYTHON_TOOLS:
|
||||
rules.append("Use Python execution for calculations, data processing, and focused local experiments.")
|
||||
if tools & (_WEB_TOOLS | _BROWSER_TOOLS):
|
||||
rules.append("Use web/browser tools only when current external information or direct page interaction is needed.")
|
||||
if tools & _TODO_TOOLS:
|
||||
rules.append("Use the task ledger for multi-step work; keep one item in progress and update it as work changes.")
|
||||
if tools & _SUBAGENT_TOOLS:
|
||||
rules.append("Use subagents only for bounded parallel work, isolation, verification, or clearly separate scopes.")
|
||||
if self.company_mode and tools & _COMPANY_COLLABORATION_TOOLS:
|
||||
rules.append("Use company collaboration tools only for the active work-item coordination surface and only when available this turn.")
|
||||
if self._has_parallel_read_surface():
|
||||
rules.append("Run independent read/search/context-gathering tool calls in parallel when there is no dependency between them.")
|
||||
return rules
|
||||
|
||||
def _has_parallel_read_surface(self) -> bool:
|
||||
read_like_count = len(self.allowed_tools & (_FILE_READ_TOOLS | _WEB_TOOLS | _BROWSER_TOOLS))
|
||||
return read_like_count >= 2
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Typed prompt harness objects."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class RuntimeArtifact:
|
||||
artifact_type: str
|
||||
title: str
|
||||
content: str
|
||||
scope: str = "runtime"
|
||||
metadata: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_record(self, *, content_hash: str) -> dict[str, Any]:
|
||||
return {
|
||||
"type": self.artifact_type,
|
||||
"title": self.title,
|
||||
"content": self.content,
|
||||
"scope": self.scope,
|
||||
"content_hash": content_hash,
|
||||
"metadata": dict(self.metadata),
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class PromptHarnessOutput:
|
||||
system_prompt: str
|
||||
runtime_policy_messages: list[dict[str, Any]] = field(default_factory=list)
|
||||
workspace_context_messages: list[dict[str, Any]] = field(default_factory=list)
|
||||
dynamic_messages: list[dict[str, Any]] = field(default_factory=list)
|
||||
artifact_messages: list[dict[str, Any]] = field(default_factory=list)
|
||||
static_section_ids: list[str] = field(default_factory=list)
|
||||
dynamic_section_ids: list[str] = field(default_factory=list)
|
||||
artifact_manifest: list[dict[str, Any]] = field(default_factory=list)
|
||||
artifact_hashes: dict[str, str] = field(default_factory=dict)
|
||||
Reference in New Issue
Block a user