Skip to content

Commit 765c3a0

Browse files
patricka3125claude
andcommitted
refactor: replace loaded_profile passthrough with skill_prompt string
Move skill catalog injection from pre-enriched AgentProfile objects to a simple string passed through the provider chain. The service layer builds the skill catalog text via _build_skill_catalog() and passes it as skill_prompt to providers. Each provider appends it to the system prompt using BaseProvider._apply_skill_prompt() during command construction. This addresses PR #145 comment 3: providers no longer need to know about AgentProfile enrichment or skill metadata. The contract is a plain string. Changes: - Add skill_prompt param and _apply_skill_prompt() helper to BaseProvider - Replace loaded_profile with skill_prompt in all 7 providers and manager - Remove _enrich_profile_with_skills from terminal_service - Remove AgentProfile imports from providers that no longer need them Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6573ebb commit 765c3a0

File tree

13 files changed

+97
-91
lines changed

13 files changed

+97
-91
lines changed

src/cli_agent_orchestrator/providers/base.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def __init__(
4747
session_name: str,
4848
window_name: str,
4949
allowed_tools: Optional[List[str]] = None,
50+
skill_prompt: Optional[str] = None,
5051
):
5152
"""Initialize provider with terminal context.
5253
@@ -55,12 +56,15 @@ def __init__(
5556
session_name: Name of the tmux session
5657
window_name: Name of the tmux window
5758
allowed_tools: Optional list of CAO tool names the agent is allowed to use
59+
skill_prompt: Optional skill catalog text built by the service layer.
60+
Providers append this to the system prompt when building their CLI command.
5861
"""
5962
self.terminal_id = terminal_id
6063
self.session_name = session_name
6164
self.window_name = window_name
6265
self._status = TerminalStatus.IDLE
6366
self._allowed_tools: Optional[List[str]] = allowed_tools
67+
self._skill_prompt: Optional[str] = skill_prompt
6468

6569
@property
6670
def status(self) -> TerminalStatus:
@@ -161,6 +165,22 @@ def mark_input_received(self) -> None:
161165
"""
162166
pass
163167

168+
def _apply_skill_prompt(self, system_prompt: str) -> str:
169+
"""Append skill catalog text to a system prompt if available.
170+
171+
Args:
172+
system_prompt: The base system prompt string.
173+
174+
Returns:
175+
The system prompt with skill catalog appended, or unchanged if
176+
no skill_prompt was provided.
177+
"""
178+
if not self._skill_prompt:
179+
return system_prompt
180+
if system_prompt:
181+
return f"{system_prompt}\n\n{self._skill_prompt}"
182+
return self._skill_prompt
183+
164184
def _update_status(self, status: TerminalStatus) -> None:
165185
"""Update internal status."""
166186
self._status = status

src/cli_agent_orchestrator/providers/claude_code.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from typing import Optional
1111

1212
from cli_agent_orchestrator.clients.tmux import tmux_client
13-
from cli_agent_orchestrator.models.agent_profile import AgentProfile
1413
from cli_agent_orchestrator.models.terminal import TerminalStatus
1514
from cli_agent_orchestrator.providers.base import BaseProvider
1615
from cli_agent_orchestrator.utils.agent_profiles import load_agent_profile
@@ -54,18 +53,12 @@ def __init__(
5453
window_name: str,
5554
agent_profile: Optional[str] = None,
5655
allowed_tools: Optional[list] = None,
57-
loaded_profile: Optional[AgentProfile] = None,
56+
skill_prompt: Optional[str] = None,
5857
):
59-
"""Initialize provider state.
60-
61-
``loaded_profile`` lets terminal_service pass a pre-enriched profile
62-
so providers reuse injected skill catalog content instead of reloading
63-
the profile from disk.
64-
"""
65-
super().__init__(terminal_id, session_name, window_name, allowed_tools)
58+
"""Initialize provider state."""
59+
super().__init__(terminal_id, session_name, window_name, allowed_tools, skill_prompt)
6660
self._initialized = False
6761
self._agent_profile = agent_profile
68-
self._loaded_profile = loaded_profile
6962

7063
def _build_claude_command(self) -> str:
7164
"""Build Claude Code command with agent profile if provided.
@@ -81,10 +74,11 @@ def _build_claude_command(self) -> str:
8174

8275
if self._agent_profile is not None:
8376
try:
84-
profile = self._loaded_profile or load_agent_profile(self._agent_profile)
77+
profile = load_agent_profile(self._agent_profile)
8578

8679
# Add system prompt - escape newlines to prevent tmux chunking issues
8780
system_prompt = profile.system_prompt if profile.system_prompt is not None else ""
81+
system_prompt = self._apply_skill_prompt(system_prompt)
8882
if system_prompt:
8983
# Replace actual newlines with \n escape sequences
9084
# This prevents tmux send_keys chunking from breaking the command

src/cli_agent_orchestrator/providers/codex.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from typing import Optional
88

99
from cli_agent_orchestrator.clients.tmux import tmux_client
10-
from cli_agent_orchestrator.models.agent_profile import AgentProfile
1110
from cli_agent_orchestrator.models.terminal import TerminalStatus
1211
from cli_agent_orchestrator.providers.base import BaseProvider
1312
from cli_agent_orchestrator.utils.agent_profiles import load_agent_profile
@@ -121,18 +120,12 @@ def __init__(
121120
window_name: str,
122121
agent_profile: Optional[str] = None,
123122
allowed_tools: Optional[list] = None,
124-
loaded_profile: Optional[AgentProfile] = None,
123+
skill_prompt: Optional[str] = None,
125124
):
126-
"""Initialize provider state.
127-
128-
``loaded_profile`` lets terminal_service pass a pre-enriched profile
129-
so providers reuse injected skill catalog content instead of reloading
130-
the profile from disk.
131-
"""
132-
super().__init__(terminal_id, session_name, window_name, allowed_tools)
125+
"""Initialize provider state."""
126+
super().__init__(terminal_id, session_name, window_name, allowed_tools, skill_prompt)
133127
self._initialized = False
134128
self._agent_profile = agent_profile
135-
self._loaded_profile = loaded_profile
136129

137130
def _build_codex_command(self) -> str:
138131
"""Build Codex command with agent profile if provided.
@@ -149,9 +142,10 @@ def _build_codex_command(self) -> str:
149142

150143
if self._agent_profile is not None:
151144
try:
152-
profile = self._loaded_profile or load_agent_profile(self._agent_profile)
145+
profile = load_agent_profile(self._agent_profile)
153146

154147
system_prompt = profile.system_prompt if profile.system_prompt is not None else ""
148+
system_prompt = self._apply_skill_prompt(system_prompt)
155149

156150
# Prepend security constraints for soft enforcement (Codex has no
157151
# native tool restriction mechanism). Only applied when tool

src/cli_agent_orchestrator/providers/copilot_cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ def __init__(
5151
window_name: str,
5252
agent_profile: Optional[str] = None,
5353
allowed_tools: Optional[list] = None,
54+
skill_prompt: Optional[str] = None,
5455
):
55-
super().__init__(terminal_id, session_name, window_name, allowed_tools)
56+
super().__init__(terminal_id, session_name, window_name, allowed_tools, skill_prompt)
5657
self._initialized = False
5758
self._agent_profile = agent_profile
5859
self._copilot_help_text_cache: Optional[str] = None

src/cli_agent_orchestrator/providers/gemini_cli.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
from typing import Optional
3838

3939
from cli_agent_orchestrator.clients.tmux import tmux_client
40-
from cli_agent_orchestrator.models.agent_profile import AgentProfile
4140
from cli_agent_orchestrator.models.terminal import TerminalStatus
4241
from cli_agent_orchestrator.providers.base import BaseProvider
4342
from cli_agent_orchestrator.utils.agent_profiles import load_agent_profile
@@ -155,18 +154,12 @@ def __init__(
155154
window_name: str,
156155
agent_profile: Optional[str] = None,
157156
allowed_tools: Optional[list] = None,
158-
loaded_profile: Optional[AgentProfile] = None,
157+
skill_prompt: Optional[str] = None,
159158
):
160-
"""Initialize provider state.
161-
162-
``loaded_profile`` lets terminal_service pass a pre-enriched profile
163-
so providers reuse injected skill catalog content instead of reloading
164-
the profile from disk.
165-
"""
166-
super().__init__(terminal_id, session_name, window_name, allowed_tools)
159+
"""Initialize provider state."""
160+
super().__init__(terminal_id, session_name, window_name, allowed_tools, skill_prompt)
167161
self._initialized = False
168162
self._agent_profile = agent_profile
169-
self._loaded_profile = loaded_profile
170163
# Track whether -i (prompt-interactive) flag is used so initialize()
171164
# can wait for COMPLETED instead of IDLE. When -i is used, Gemini
172165
# processes the system prompt as the first user message and produces
@@ -223,7 +216,7 @@ def _build_gemini_command(self) -> str:
223216

224217
if self._agent_profile is not None:
225218
try:
226-
profile = self._loaded_profile or load_agent_profile(self._agent_profile)
219+
profile = load_agent_profile(self._agent_profile)
227220

228221
# System prompt injection: write to GEMINI.md so Gemini loads it
229222
# as persistent project context on startup.
@@ -238,6 +231,7 @@ def _build_gemini_command(self) -> str:
238231
# A short ``-i`` role acknowledgment ensures the model adopts the role
239232
# strongly without triggering exploration behavior.
240233
system_prompt = profile.system_prompt if profile.system_prompt is not None else ""
234+
system_prompt = self._apply_skill_prompt(system_prompt)
241235
if system_prompt:
242236
# Write full system prompt to GEMINI.md for persistent context.
243237
working_dir = tmux_client.get_pane_working_directory(

src/cli_agent_orchestrator/providers/kimi_cli.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
from typing import Optional
3737

3838
from cli_agent_orchestrator.clients.tmux import tmux_client
39-
from cli_agent_orchestrator.models.agent_profile import AgentProfile
4039
from cli_agent_orchestrator.models.terminal import TerminalStatus
4140
from cli_agent_orchestrator.providers.base import BaseProvider
4241
from cli_agent_orchestrator.utils.agent_profiles import load_agent_profile
@@ -142,18 +141,12 @@ def __init__(
142141
window_name: str,
143142
agent_profile: Optional[str] = None,
144143
allowed_tools: Optional[list] = None,
145-
loaded_profile: Optional[AgentProfile] = None,
144+
skill_prompt: Optional[str] = None,
146145
):
147-
"""Initialize provider state.
148-
149-
``loaded_profile`` lets terminal_service pass a pre-enriched profile
150-
so providers reuse injected skill catalog content instead of reloading
151-
the profile from disk.
152-
"""
153-
super().__init__(terminal_id, session_name, window_name, allowed_tools)
146+
"""Initialize provider state."""
147+
super().__init__(terminal_id, session_name, window_name, allowed_tools, skill_prompt)
154148
self._initialized = False
155149
self._agent_profile = agent_profile
156-
self._loaded_profile = loaded_profile
157150
# Track temp directory for cleanup (created when agent profile needs temp files)
158151
self._temp_dir: Optional[str] = None
159152
# Latching flag: set True when user input box (╭─) is detected in ANY
@@ -199,12 +192,13 @@ def _build_kimi_command(self) -> str:
199192

200193
if self._agent_profile is not None:
201194
try:
202-
profile = self._loaded_profile or load_agent_profile(self._agent_profile)
195+
profile = load_agent_profile(self._agent_profile)
203196

204197
# Build agent file from profile's system prompt.
205198
# Kimi uses YAML agent files with a system_prompt_path pointing
206199
# to a markdown file. We create both in the temp directory.
207200
system_prompt = profile.system_prompt if profile.system_prompt is not None else ""
201+
system_prompt = self._apply_skill_prompt(system_prompt)
208202

209203
# Prepend security constraints for soft enforcement (Kimi CLI has no
210204
# native tool restriction mechanism). Only applied when tool

src/cli_agent_orchestrator/providers/kiro_cli.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def __init__(
8181
window_name: str,
8282
agent_profile: str,
8383
allowed_tools: Optional[list] = None,
84+
skill_prompt: Optional[str] = None,
8485
):
8586
"""Initialize Kiro CLI provider with terminal context.
8687
@@ -90,8 +91,9 @@ def __init__(
9091
window_name: Name of the tmux window
9192
agent_profile: Name of the Kiro agent profile to use (e.g., "developer")
9293
allowed_tools: Optional list of CAO tool names the agent is allowed to use
94+
skill_prompt: Optional skill catalog text (unused — Kiro uses native profiles)
9395
"""
94-
super().__init__(terminal_id, session_name, window_name, allowed_tools)
96+
super().__init__(terminal_id, session_name, window_name, allowed_tools, skill_prompt)
9597
self._initialized = False
9698
self._agent_profile = agent_profile
9799

src/cli_agent_orchestrator/providers/manager.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from typing import Dict, List, Optional
55

66
from cli_agent_orchestrator.clients.database import get_terminal_metadata
7-
from cli_agent_orchestrator.models.agent_profile import AgentProfile
87
from cli_agent_orchestrator.models.provider import ProviderType
98
from cli_agent_orchestrator.providers.base import BaseProvider
109
from cli_agent_orchestrator.providers.claude_code import ClaudeCodeProvider
@@ -32,7 +31,7 @@ def create_provider(
3231
tmux_window: str,
3332
agent_profile: Optional[str] = None,
3433
allowed_tools: Optional[List[str]] = None,
35-
loaded_profile: Optional[AgentProfile] = None,
34+
skill_prompt: Optional[str] = None,
3635
) -> BaseProvider:
3736
"""Create and store provider instance."""
3837
try:
@@ -41,13 +40,15 @@ def create_provider(
4140
if not agent_profile:
4241
raise ValueError("Q CLI provider requires agent_profile parameter")
4342
provider = QCliProvider(
44-
terminal_id, tmux_session, tmux_window, agent_profile, allowed_tools
43+
terminal_id, tmux_session, tmux_window, agent_profile, allowed_tools,
44+
skill_prompt=skill_prompt,
4545
)
4646
elif provider_type == ProviderType.KIRO_CLI.value:
4747
if not agent_profile:
4848
raise ValueError("Kiro CLI provider requires agent_profile parameter")
4949
provider = KiroCliProvider(
50-
terminal_id, tmux_session, tmux_window, agent_profile, allowed_tools
50+
terminal_id, tmux_session, tmux_window, agent_profile, allowed_tools,
51+
skill_prompt=skill_prompt,
5152
)
5253
elif provider_type == ProviderType.CLAUDE_CODE.value:
5354
provider = ClaudeCodeProvider(
@@ -56,7 +57,7 @@ def create_provider(
5657
tmux_window,
5758
agent_profile,
5859
allowed_tools,
59-
loaded_profile,
60+
skill_prompt=skill_prompt,
6061
)
6162
elif provider_type == ProviderType.CODEX.value:
6263
provider = CodexProvider(
@@ -65,11 +66,12 @@ def create_provider(
6566
tmux_window,
6667
agent_profile,
6768
allowed_tools,
68-
loaded_profile,
69+
skill_prompt=skill_prompt,
6970
)
7071
elif provider_type == ProviderType.COPILOT_CLI.value:
7172
provider = CopilotCliProvider(
72-
terminal_id, tmux_session, tmux_window, agent_profile, allowed_tools
73+
terminal_id, tmux_session, tmux_window, agent_profile, allowed_tools,
74+
skill_prompt=skill_prompt,
7375
)
7476
elif provider_type == ProviderType.GEMINI_CLI.value:
7577
provider = GeminiCliProvider(
@@ -78,7 +80,7 @@ def create_provider(
7880
tmux_window,
7981
agent_profile,
8082
allowed_tools,
81-
loaded_profile,
83+
skill_prompt=skill_prompt,
8284
)
8385
elif provider_type == ProviderType.KIMI_CLI.value:
8486
provider = KimiCliProvider(
@@ -87,7 +89,7 @@ def create_provider(
8789
tmux_window,
8890
agent_profile,
8991
allowed_tools,
90-
loaded_profile,
92+
skill_prompt=skill_prompt,
9193
)
9294
else:
9395
raise ValueError(f"Unknown provider type: {provider_type}")

src/cli_agent_orchestrator/providers/q_cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ def __init__(
3434
window_name: str,
3535
agent_profile: str,
3636
allowed_tools: Optional[list] = None,
37+
skill_prompt: Optional[str] = None,
3738
):
38-
super().__init__(terminal_id, session_name, window_name, allowed_tools)
39+
super().__init__(terminal_id, session_name, window_name, allowed_tools, skill_prompt)
3940
# TODO: remove the ._initialized if it's not referenced anywhere
4041
self._initialized = False
4142
self._agent_profile = agent_profile

src/cli_agent_orchestrator/services/terminal_service.py

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -91,20 +91,6 @@ def _build_skill_catalog(profile: AgentProfile) -> str:
9191
)
9292

9393

94-
def _enrich_profile_with_skills(profile: AgentProfile) -> AgentProfile:
95-
"""Append the declared skill catalog to the profile system prompt."""
96-
skill_catalog = _build_skill_catalog(profile)
97-
if not skill_catalog:
98-
return profile
99-
100-
enriched_profile = profile.model_copy(deep=True)
101-
base_prompt = enriched_profile.system_prompt or ""
102-
enriched_profile.system_prompt = (
103-
f"{base_prompt}\n\n{skill_catalog}" if base_prompt else skill_catalog
104-
)
105-
return enriched_profile
106-
107-
10894
def create_terminal(
10995
provider: str,
11096
agent_profile: str,
@@ -171,10 +157,10 @@ def create_terminal(
171157
terminal_id, session_name, window_name, provider, agent_profile, allowed_tools
172158
)
173159

174-
# Step 3b: Load the profile once for allowed tool resolution and optional
175-
# skill catalog injection before provider initialization.
160+
# Step 3b: Load the profile once for allowed tool resolution and
161+
# skill catalog generation before provider initialization.
176162
profile = load_agent_profile(agent_profile)
177-
enriched_profile = _enrich_profile_with_skills(profile)
163+
skill_prompt = _build_skill_catalog(profile)
178164

179165
# Step 3c: Resolve allowed_tools from profile if not explicitly provided
180166
if allowed_tools is None:
@@ -194,7 +180,7 @@ def create_terminal(
194180
window_name,
195181
agent_profile,
196182
allowed_tools,
197-
loaded_profile=enriched_profile,
183+
skill_prompt=skill_prompt,
198184
)
199185
provider_instance.initialize()
200186

0 commit comments

Comments
 (0)