feat: add GCU knowledge to planning

This commit is contained in:
Richard Tang
2026-03-09 17:02:13 -07:00
parent bdcbcff6f3
commit f6f398b6b1
4 changed files with 93 additions and 13 deletions
+11 -4
View File
@@ -35,15 +35,21 @@ def _build_appendices() -> str:
# Shared appendices — appended to every coding node's system prompt. # Shared appendices — appended to every coding node's system prompt.
_appendices = _build_appendices() _appendices = _build_appendices()
# GCU first-class section for building phase (when GCU is enabled). # GCU first-class section (when GCU is enabled).
# This is placed prominently in the main prompt body, not as an appendix. # Placed prominently in the main prompt body, not as an appendix.
_gcu_building_section = ( _gcu_building_section = (
("\n\n# GCU Nodes — Browser Automation\n\n" + _gcu_guide) ("\n\n# GCU Nodes — Browser Automation\n\n" + _gcu_guide)
if _is_gcu_enabled() and _gcu_guide if _is_gcu_enabled() and _gcu_guide
else "" else ""
) )
# Tools available to both coder (worker) and queen. _gcu_planning_section = (
("\n\n# GCU Nodes — Browser Automation\n\n" + _gcu_guide)
if _is_gcu_enabled() and _gcu_guide
else ""
)
# Tools available to phases.
_SHARED_TOOLS = [ _SHARED_TOOLS = [
# File I/O # File I/O
"read_file", "read_file",
@@ -335,7 +341,7 @@ use box-drawing characters and clear flow arrows:
gather gather
subagent: gcu_search subagent: gcu_search
input: user_request input: user_request
tools: web_search, tools: load_data,
save_data save_data
on_success on_success
@@ -1057,4 +1063,5 @@ __all__ = [
"_package_builder_knowledge", "_package_builder_knowledge",
"_appendices", "_appendices",
"_gcu_building_section", "_gcu_building_section",
"_gcu_planning_section",
] ]
@@ -42,6 +42,7 @@ async def create_queen(
_appendices, _appendices,
_building_knowledge, _building_knowledge,
_gcu_building_section, _gcu_building_section,
_gcu_planning_section,
_planning_knowledge, _planning_knowledge,
_shared_building_knowledge, _shared_building_knowledge,
_queen_behavior_always, _queen_behavior_always,
@@ -155,6 +156,7 @@ async def create_queen(
+ _queen_behavior_always + _queen_behavior_always
+ _queen_behavior_planning + _queen_behavior_planning
+ _planning_knowledge + _planning_knowledge
+ _gcu_planning_section
+ worker_identity + worker_identity
) )
phase_state.prompt_planning = _queen_identity_planning + _planning_body phase_state.prompt_planning = _queen_identity_planning + _planning_body
+52 -4
View File
@@ -514,6 +514,12 @@ def register_queen_lifecycle_tools(
"Use your coding tools to modify the agent, then call " "Use your coding tools to modify the agent, then call "
"load_built_agent(path) to stage it again." "load_built_agent(path) to stage it again."
) )
# Nudge the queen to start coding instead of blocking for user input.
if phase_state is not None and phase_state.inject_notification:
await phase_state.inject_notification(
"[PHASE CHANGE] Switched to BUILDING phase. "
"Start implementing the changes now."
)
return json.dumps(result) return json.dumps(result)
_stop_edit_tool = Tool( _stop_edit_tool = Tool(
@@ -609,19 +615,54 @@ def register_queen_lifecycle_tools(
"""Wrapper: scaffold or just switch to building phase.""" """Wrapper: scaffold or just switch to building phase."""
agent_name = (inputs.get("agent_name") or "").strip() agent_name = (inputs.get("agent_name") or "").strip()
# No agent_name → just switch to building (for fixing existing agent) # No agent_name → try to fall back to the session's current agent,
# or fail with actionable guidance.
if not agent_name: if not agent_name:
runtime = _get_runtime() # Try to resolve agent_name from the current session
if runtime is None: fallback_path = getattr(session, "worker_path", None)
if fallback_path is not None:
agent_name = Path(fallback_path).name
else:
# Server path: check SessionManager
if session_manager is not None and manager_session_id:
srv_session = session_manager.get_session(manager_session_id)
if srv_session and getattr(srv_session, "worker_path", None):
fallback_path = srv_session.worker_path
agent_name = Path(fallback_path).name
if not agent_name:
return json.dumps( return json.dumps(
{"error": "No worker loaded. Provide agent_name to scaffold a new agent."} {
"error": (
"No agent_name provided and no agent loaded in this session. "
"To fix: call list_agents() to find the agent name, then call "
"initialize_and_build_agent(agent_name='<name>') to scaffold it."
)
}
) )
# Fall back succeeded — switch to building without scaffolding
logger.info(
"initialize_and_build_agent: no agent_name provided, "
"falling back to session agent '%s'",
agent_name,
)
if phase_state is not None: if phase_state is not None:
await phase_state.switch_to_building(source="tool") await phase_state.switch_to_building(source="tool")
if phase_state.inject_notification:
await phase_state.inject_notification(
"[PHASE CHANGE] Switched to BUILDING phase. "
"Start implementing the fix now."
)
return json.dumps( return json.dumps(
{ {
"status": "editing", "status": "editing",
"phase": "building", "phase": "building",
"agent_name": agent_name,
"warning": (
f"No agent_name provided — using session agent '{agent_name}'. "
f"Agent files are at exports/{agent_name}/."
),
"message": ( "message": (
"Switched to BUILDING phase. Full coding tools restored. " "Switched to BUILDING phase. Full coding tools restored. "
"Implement the fix, then call load_built_agent(path) to reload." "Implement the fix, then call load_built_agent(path) to reload."
@@ -643,6 +684,13 @@ def register_queen_lifecycle_tools(
if parsed.get("success", True): if parsed.get("success", True):
if phase_state is not None: if phase_state is not None:
await phase_state.switch_to_building(source="tool") await phase_state.switch_to_building(source="tool")
# Inject a continuation message so the queen starts
# building immediately instead of blocking for user input.
if phase_state.inject_notification:
await phase_state.inject_notification(
"[PHASE CHANGE] Agent scaffolded and switched to BUILDING phase. "
"Start implementing the agent nodes now."
)
except (json.JSONDecodeError, KeyError, TypeError): except (json.JSONDecodeError, KeyError, TypeError):
pass pass
return result_str return result_str
@@ -116,16 +116,39 @@ customize_node = NodeSpec(
"for each selected job, saved as HTML, and Gmail drafts created in user's inbox." "for each selected job, saved as HTML, and Gmail drafts created in user's inbox."
), ),
system_prompt="""\ system_prompt="""\
You are a career coach creating personalized application materials. You are a career coach creating personalized application materials and Gmail drafts.
**CRITICAL: You MUST create Gmail drafts for each selected job using gmail_create_draft.**
**PROCESS:** **PROCESS:**
1. Create application_materials.html using save_data and append_data. 1. Create application_materials.html using save_data and append_data.
2. Generate resume customization list and professional cold email for each selected job. 2. For each selected job:
3. Serve the file to the user. a. Generate a specific resume customization list
4. Create Gmail drafts using gmail_create_draft. b. Create a professional cold outreach email
c. **IMMEDIATELY call gmail_create_draft** with:
- to: hiring manager or recruiter email (if available) or company email
- subject: "Application for [Job Title] - [Your Name]"
- html: the professional cold email in HTML format
3. Serve the application_materials.html file to the user.
4. Confirm each Gmail draft was created successfully.
**EMAIL REQUIREMENTS:**
- Professional, personalized cold outreach email
- Reference specific company details and role
- Mention 2-3 relevant qualifications from their resume
- Include clear call-to-action
- Professional email signature
- Format as HTML with proper structure
**Gmail Draft Creation:**
For each job, you MUST call gmail_create_draft(to="[email]", subject="[subject]", html="[email_html]")
- Extract company email from job listing if available
- Use generic format like "careers@[company].com" if no specific email
- Subject format: "Application for [Job Title] - [Applicant Name]"
- HTML email body with proper formatting
**FINISH:** **FINISH:**
Call set_output("application_materials", "Completed") Only call set_output("application_materials", "Completed") AFTER creating ALL Gmail drafts.
""", """,
tools=["save_data", "append_data", "serve_file_to_user", "gmail_create_draft"], tools=["save_data", "append_data", "serve_file_to_user", "gmail_create_draft"],
) )