arkaos 2.0.1 → 2.0.3
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.
- package/VERSION +1 -1
- package/config/constitution.yaml +6 -0
- package/config/hooks/user-prompt-submit-v2.sh +33 -40
- package/core/budget/__init__.py +6 -0
- package/core/budget/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/budget/__pycache__/manager.cpython-313.pyc +0 -0
- package/core/budget/__pycache__/schema.cpython-313.pyc +0 -0
- package/core/budget/manager.py +193 -0
- package/core/budget/schema.py +82 -0
- package/core/knowledge/__init__.py +6 -0
- package/core/knowledge/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/chunker.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/embedder.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/indexer.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/core/knowledge/chunker.py +121 -0
- package/core/knowledge/embedder.py +52 -0
- package/core/knowledge/indexer.py +97 -0
- package/core/knowledge/vector_store.py +213 -0
- package/core/obsidian/__init__.py +6 -0
- package/core/obsidian/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/obsidian/__pycache__/templates.cpython-313.pyc +0 -0
- package/core/obsidian/__pycache__/writer.cpython-313.pyc +0 -0
- package/core/obsidian/templates.py +76 -0
- package/core/obsidian/writer.py +148 -0
- package/core/orchestration/__init__.py +6 -0
- package/core/orchestration/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/orchestration/__pycache__/patterns.cpython-313.pyc +0 -0
- package/core/orchestration/__pycache__/protocol.cpython-313.pyc +0 -0
- package/core/orchestration/patterns.py +136 -0
- package/core/orchestration/protocol.py +96 -0
- package/core/runtime/__pycache__/subagent.cpython-313.pyc +0 -0
- package/core/runtime/subagent.py +5 -0
- package/core/squads/__pycache__/schema.cpython-313.pyc +0 -0
- package/core/squads/schema.py +3 -0
- package/core/squads/templates/project-squad.yaml +28 -0
- package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
- package/core/synapse/engine.py +5 -1
- package/core/synapse/layers.py +95 -9
- package/core/tasks/__pycache__/schema.cpython-313.pyc +0 -0
- package/core/tasks/schema.py +7 -0
- package/core/workflow/__pycache__/engine.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/schema.cpython-313.pyc +0 -0
- package/core/workflow/engine.py +44 -0
- package/core/workflow/schema.py +1 -0
- package/departments/dev/agents/research-assistant.yaml +51 -0
- package/departments/kb/agents/data-collector.yaml +51 -0
- package/departments/ops/agents/doc-writer.yaml +51 -0
- package/departments/pm/agents/pm-director.yaml +1 -1
- package/installer/cli.js +36 -0
- package/installer/init.js +105 -0
- package/installer/migrate.js +4 -1
- package/package.json +1 -1
- package/pyproject.toml +5 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Built-in orchestration patterns.
|
|
2
|
+
|
|
3
|
+
Four patterns adapted from claude-skills orchestration protocol,
|
|
4
|
+
mapped to ArkaOS department/agent/skill system.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from core.orchestration.protocol import OrchestrationPattern, PatternType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
SOLO_SPRINT = OrchestrationPattern(
|
|
11
|
+
type=PatternType.SOLO_SPRINT,
|
|
12
|
+
name="Solo Sprint",
|
|
13
|
+
description="One department lead drives a multi-phase sprint, pulling skills from their squad. Fast, focused execution.",
|
|
14
|
+
when_to_use=[
|
|
15
|
+
"Single-domain task with clear scope",
|
|
16
|
+
"Time-constrained delivery (< 1 week)",
|
|
17
|
+
"One department has all required expertise",
|
|
18
|
+
],
|
|
19
|
+
structure="Lead → Phase 1 (skills A,B) → Phase 2 (skills C,D) → Quality Gate → Deliver",
|
|
20
|
+
example=(
|
|
21
|
+
"Objective: Launch MVP landing page\n"
|
|
22
|
+
"Lead: Ines (Landing)\n"
|
|
23
|
+
"Phase 1: /landing copy-framework + /landing funnel-design\n"
|
|
24
|
+
"Phase 2: /landing page-build + /landing seo-optimize\n"
|
|
25
|
+
"Phase 3: Quality Gate → Ship"
|
|
26
|
+
),
|
|
27
|
+
anti_patterns=[
|
|
28
|
+
"Using for cross-department work (use Multi-Agent Handoff instead)",
|
|
29
|
+
"Skipping Quality Gate because 'it's just one department'",
|
|
30
|
+
],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
DOMAIN_DEEP_DIVE = OrchestrationPattern(
|
|
34
|
+
type=PatternType.DOMAIN_DEEP_DIVE,
|
|
35
|
+
name="Domain Deep-Dive",
|
|
36
|
+
description="One agent, multiple stacked skills for thorough analysis. Deep expertise over breadth.",
|
|
37
|
+
when_to_use=[
|
|
38
|
+
"Complex technical audit or review",
|
|
39
|
+
"Due diligence or compliance assessment",
|
|
40
|
+
"Research requiring depth in one area",
|
|
41
|
+
],
|
|
42
|
+
structure="Agent → Skill 1 (foundation) → Skill 2 (analysis) → Skill 3 (recommendations) → Report",
|
|
43
|
+
example=(
|
|
44
|
+
"Objective: Full security assessment\n"
|
|
45
|
+
"Agent: Bruno (Security Engineer)\n"
|
|
46
|
+
"Skills stacked:\n"
|
|
47
|
+
" 1. /dev security-audit (OWASP scan)\n"
|
|
48
|
+
" 2. /dev dependency-audit (CVE check)\n"
|
|
49
|
+
" 3. /dev red-team (attack simulation)\n"
|
|
50
|
+
" 4. /dev ai-security (LLM-specific risks)\n"
|
|
51
|
+
"Output: Consolidated security report with severity rankings"
|
|
52
|
+
),
|
|
53
|
+
anti_patterns=[
|
|
54
|
+
"Stacking unrelated skills (each skill should build on previous)",
|
|
55
|
+
"Skipping the foundational skill and going straight to advanced",
|
|
56
|
+
"No consolidated output (each skill reports independently)",
|
|
57
|
+
],
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
MULTI_AGENT_HANDOFF = OrchestrationPattern(
|
|
61
|
+
type=PatternType.MULTI_AGENT_HANDOFF,
|
|
62
|
+
name="Multi-Agent Handoff",
|
|
63
|
+
description="Work flows between departments with structured handoffs. Each department adds its expertise.",
|
|
64
|
+
when_to_use=[
|
|
65
|
+
"Cross-department projects (product launch, brand + marketing)",
|
|
66
|
+
"Sequential expertise needed (strategy → dev → marketing)",
|
|
67
|
+
"Each phase requires different domain knowledge",
|
|
68
|
+
],
|
|
69
|
+
structure="Dept A → Handoff → Dept B → Handoff → Dept C → Quality Gate → Deliver",
|
|
70
|
+
example=(
|
|
71
|
+
"Objective: Launch new SaaS product\n"
|
|
72
|
+
"Phase 1: Tomas (Strategy) → /strat bmc + /strat five-forces\n"
|
|
73
|
+
" Handoff: Market validation, pricing strategy, competitive position\n"
|
|
74
|
+
"Phase 2: Paulo (Dev) → /dev spec + /dev feature\n"
|
|
75
|
+
" Handoff: MVP built, deployed to staging\n"
|
|
76
|
+
"Phase 3: Luna (Marketing) → /mkt growth-plan + /mkt email-sequence\n"
|
|
77
|
+
" Handoff: Landing page, email funnel, launch plan\n"
|
|
78
|
+
"Phase 4: Quality Gate → Launch"
|
|
79
|
+
),
|
|
80
|
+
anti_patterns=[
|
|
81
|
+
"No handoff context (next department starts from zero)",
|
|
82
|
+
"Too many departments (>4 phases becomes unwieldy)",
|
|
83
|
+
"Skipping departments that should review (e.g., security for a public launch)",
|
|
84
|
+
],
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
SKILL_CHAIN = OrchestrationPattern(
|
|
88
|
+
type=PatternType.SKILL_CHAIN,
|
|
89
|
+
name="Skill Chain",
|
|
90
|
+
description="Sequential procedural skills with no specific agent identity. Pure execution pipeline.",
|
|
91
|
+
when_to_use=[
|
|
92
|
+
"Procedural work with well-defined inputs/outputs",
|
|
93
|
+
"Automated pipelines (generate → validate → publish)",
|
|
94
|
+
"No judgment calls needed, just execution",
|
|
95
|
+
],
|
|
96
|
+
structure="Skill A (input) → Skill B (transform) → Skill C (output) → Done",
|
|
97
|
+
example=(
|
|
98
|
+
"Objective: Content production pipeline\n"
|
|
99
|
+
"Chain:\n"
|
|
100
|
+
" 1. /content hook-write → 10 headline options\n"
|
|
101
|
+
" 2. python scripts/tools/headline_scorer.py → scored + ranked\n"
|
|
102
|
+
" 3. /content viral-design → full post with winning hook\n"
|
|
103
|
+
" 4. /mkt social-strategy → distribution plan\n"
|
|
104
|
+
"No persona needed — pure skill execution"
|
|
105
|
+
),
|
|
106
|
+
anti_patterns=[
|
|
107
|
+
"Using for work that needs judgment (use Solo Sprint or Handoff)",
|
|
108
|
+
"Long chains with no validation checkpoints",
|
|
109
|
+
"Mixing judgment skills with procedural skills",
|
|
110
|
+
],
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
PATTERNS = {
|
|
115
|
+
PatternType.SOLO_SPRINT: SOLO_SPRINT,
|
|
116
|
+
PatternType.DOMAIN_DEEP_DIVE: DOMAIN_DEEP_DIVE,
|
|
117
|
+
PatternType.MULTI_AGENT_HANDOFF: MULTI_AGENT_HANDOFF,
|
|
118
|
+
PatternType.SKILL_CHAIN: SKILL_CHAIN,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def select_pattern(
|
|
123
|
+
departments_involved: int,
|
|
124
|
+
needs_judgment: bool,
|
|
125
|
+
is_sequential: bool,
|
|
126
|
+
) -> PatternType:
|
|
127
|
+
"""Recommend an orchestration pattern based on task characteristics."""
|
|
128
|
+
if departments_involved == 1 and needs_judgment:
|
|
129
|
+
return PatternType.SOLO_SPRINT
|
|
130
|
+
if departments_involved == 1 and not needs_judgment:
|
|
131
|
+
return PatternType.SKILL_CHAIN
|
|
132
|
+
if departments_involved > 1 and is_sequential:
|
|
133
|
+
return PatternType.MULTI_AGENT_HANDOFF
|
|
134
|
+
if departments_involved == 1:
|
|
135
|
+
return PatternType.DOMAIN_DEEP_DIVE
|
|
136
|
+
return PatternType.MULTI_AGENT_HANDOFF
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Orchestration protocol schemas.
|
|
2
|
+
|
|
3
|
+
Defines the structure for multi-agent coordination across departments.
|
|
4
|
+
Four patterns: Solo Sprint, Domain Deep-Dive, Multi-Agent Handoff, Skill Chain.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PatternType(str, Enum):
|
|
14
|
+
SOLO_SPRINT = "solo_sprint"
|
|
15
|
+
DOMAIN_DEEP_DIVE = "domain_deep_dive"
|
|
16
|
+
MULTI_AGENT_HANDOFF = "multi_agent_handoff"
|
|
17
|
+
SKILL_CHAIN = "skill_chain"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PhaseHandoff(BaseModel):
|
|
21
|
+
"""Context passed between orchestration phases."""
|
|
22
|
+
phase_number: int
|
|
23
|
+
phase_name: str
|
|
24
|
+
decisions: list[str] = Field(default_factory=list)
|
|
25
|
+
artifacts: list[str] = Field(default_factory=list)
|
|
26
|
+
open_questions: list[str] = Field(default_factory=list)
|
|
27
|
+
next_department: str = ""
|
|
28
|
+
next_agent: str = ""
|
|
29
|
+
next_skills: list[str] = Field(default_factory=list)
|
|
30
|
+
|
|
31
|
+
def to_context(self) -> str:
|
|
32
|
+
"""Render handoff as context string for next phase."""
|
|
33
|
+
lines = [f"Phase {self.phase_number} ({self.phase_name}) complete."]
|
|
34
|
+
if self.decisions:
|
|
35
|
+
lines.append(f"Decisions: {', '.join(self.decisions)}")
|
|
36
|
+
if self.artifacts:
|
|
37
|
+
lines.append(f"Artifacts: {', '.join(self.artifacts)}")
|
|
38
|
+
if self.open_questions:
|
|
39
|
+
lines.append(f"Open questions: {', '.join(self.open_questions)}")
|
|
40
|
+
if self.next_department:
|
|
41
|
+
lines.append(f"Next: {self.next_department}/{self.next_agent}")
|
|
42
|
+
return "\n".join(lines)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class OrchestrationPhase(BaseModel):
|
|
46
|
+
"""A single phase in an orchestration plan."""
|
|
47
|
+
number: int
|
|
48
|
+
name: str
|
|
49
|
+
department: str
|
|
50
|
+
agent_id: str
|
|
51
|
+
skills: list[str] = Field(default_factory=list)
|
|
52
|
+
objective: str = ""
|
|
53
|
+
outputs: list[str] = Field(default_factory=list)
|
|
54
|
+
gate: str = "user_approval" # user_approval, auto, quality_gate
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class OrchestrationPlan(BaseModel):
|
|
58
|
+
"""A complete orchestration plan spanning multiple departments."""
|
|
59
|
+
objective: str
|
|
60
|
+
pattern: PatternType
|
|
61
|
+
constraints: list[str] = Field(default_factory=list)
|
|
62
|
+
success_criteria: list[str] = Field(default_factory=list)
|
|
63
|
+
phases: list[OrchestrationPhase] = Field(default_factory=list)
|
|
64
|
+
current_phase: int = 0
|
|
65
|
+
|
|
66
|
+
def next_phase(self) -> Optional[OrchestrationPhase]:
|
|
67
|
+
"""Get the next phase to execute."""
|
|
68
|
+
if self.current_phase < len(self.phases):
|
|
69
|
+
return self.phases[self.current_phase]
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
def advance(self, handoff: PhaseHandoff) -> Optional[OrchestrationPhase]:
|
|
73
|
+
"""Record handoff and advance to next phase."""
|
|
74
|
+
self.current_phase += 1
|
|
75
|
+
return self.next_phase()
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def is_complete(self) -> bool:
|
|
79
|
+
return self.current_phase >= len(self.phases)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def progress_percent(self) -> int:
|
|
83
|
+
if not self.phases:
|
|
84
|
+
return 0
|
|
85
|
+
return int((self.current_phase / len(self.phases)) * 100)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class OrchestrationPattern(BaseModel):
|
|
89
|
+
"""Definition of a coordination pattern."""
|
|
90
|
+
type: PatternType
|
|
91
|
+
name: str
|
|
92
|
+
description: str
|
|
93
|
+
when_to_use: list[str] = Field(default_factory=list)
|
|
94
|
+
structure: str = ""
|
|
95
|
+
example: str = ""
|
|
96
|
+
anti_patterns: list[str] = Field(default_factory=list)
|
|
Binary file
|
package/core/runtime/subagent.py
CHANGED
|
@@ -102,6 +102,11 @@ class SubagentDispatcher:
|
|
|
102
102
|
The dispatcher creates HandoffArtifacts from agent definitions
|
|
103
103
|
and task descriptions, then delegates to the runtime adapter
|
|
104
104
|
for actual execution.
|
|
105
|
+
|
|
106
|
+
Nesting policy: Maximum 1 level of nesting (agent -> subagent).
|
|
107
|
+
Sub-subagent dispatch is not recommended -- creates context fragmentation
|
|
108
|
+
and debugging complexity. If a subagent needs help, it should escalate
|
|
109
|
+
to its squad lead rather than spawning another subagent.
|
|
105
110
|
"""
|
|
106
111
|
|
|
107
112
|
def __init__(self) -> None:
|
|
Binary file
|
package/core/squads/schema.py
CHANGED
|
@@ -35,6 +35,9 @@ class SquadMember(BaseModel):
|
|
|
35
35
|
borrowed: bool = False # Borrowed from another department?
|
|
36
36
|
source_department: str = "" # Original department if borrowed
|
|
37
37
|
availability: float = 1.0 # 0.0-1.0, for shared agents
|
|
38
|
+
# Tier 2 agents can collaborate directly within project squads
|
|
39
|
+
# without requiring Tier 1 approval for each interaction.
|
|
40
|
+
can_collaborate_directly: bool = True
|
|
38
41
|
|
|
39
42
|
|
|
40
43
|
class SquadWorkflow(BaseModel):
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Project Squad Template
|
|
2
|
+
# Copy and customize for cross-department projects
|
|
3
|
+
id: project-{name}
|
|
4
|
+
name: "{Project Name} Squad"
|
|
5
|
+
description: "Cross-department squad for {project description}"
|
|
6
|
+
department: "" # No single department — cross-cutting
|
|
7
|
+
squad_type: project
|
|
8
|
+
topology: stream-aligned
|
|
9
|
+
|
|
10
|
+
members:
|
|
11
|
+
# Borrow from department squads
|
|
12
|
+
- agent_id: "{lead-agent-id}"
|
|
13
|
+
role: "Project Lead"
|
|
14
|
+
is_lead: true
|
|
15
|
+
borrowed: true # Borrowed from department squad
|
|
16
|
+
availability: 0.5 # 50% allocation
|
|
17
|
+
|
|
18
|
+
- agent_id: "{specialist-id}"
|
|
19
|
+
role: "Technical Implementation"
|
|
20
|
+
borrowed: true
|
|
21
|
+
availability: 0.3
|
|
22
|
+
|
|
23
|
+
# Project squads:
|
|
24
|
+
# - Created by COO (Sofia) or any Squad Lead
|
|
25
|
+
# - Agents are borrowed, not moved
|
|
26
|
+
# - Max 10 members (Two-Pizza Team)
|
|
27
|
+
# - Dissolved when project completes
|
|
28
|
+
# - Quality Gate still mandatory
|
|
Binary file
|
|
Binary file
|
package/core/synapse/engine.py
CHANGED
|
@@ -10,6 +10,7 @@ Design goals:
|
|
|
10
10
|
|
|
11
11
|
import time
|
|
12
12
|
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any
|
|
13
14
|
|
|
14
15
|
from core.synapse.layers import Layer, LayerResult, PromptContext
|
|
15
16
|
from core.synapse.cache import LayerCache
|
|
@@ -152,6 +153,7 @@ def create_default_engine(
|
|
|
152
153
|
constitution_compressed: str = "",
|
|
153
154
|
commands: list[dict] | None = None,
|
|
154
155
|
agents_registry: dict[str, dict] | None = None,
|
|
156
|
+
vector_store: Any = None,
|
|
155
157
|
) -> SynapseEngine:
|
|
156
158
|
"""Create a SynapseEngine with all 8 default layers.
|
|
157
159
|
|
|
@@ -166,7 +168,7 @@ def create_default_engine(
|
|
|
166
168
|
from core.synapse.layers import (
|
|
167
169
|
ConstitutionLayer, DepartmentLayer, AgentLayer,
|
|
168
170
|
ProjectLayer, BranchLayer, CommandHintsLayer,
|
|
169
|
-
QualityGateLayer, TimeLayer,
|
|
171
|
+
QualityGateLayer, TimeLayer, KnowledgeRetrievalLayer,
|
|
170
172
|
)
|
|
171
173
|
|
|
172
174
|
engine = SynapseEngine()
|
|
@@ -176,6 +178,8 @@ def create_default_engine(
|
|
|
176
178
|
engine.register_layer(DepartmentLayer())
|
|
177
179
|
engine.register_layer(AgentLayer(agents_registry=agents_registry))
|
|
178
180
|
engine.register_layer(ProjectLayer())
|
|
181
|
+
if vector_store is not None:
|
|
182
|
+
engine.register_layer(KnowledgeRetrievalLayer(vector_store=vector_store))
|
|
179
183
|
engine.register_layer(BranchLayer())
|
|
180
184
|
engine.register_layer(CommandHintsLayer(commands=commands))
|
|
181
185
|
engine.register_layer(QualityGateLayer())
|
package/core/synapse/layers.py
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
"""Synapse layer definitions — the
|
|
1
|
+
"""Synapse layer definitions — the 9 context layers.
|
|
2
2
|
|
|
3
3
|
Each layer extracts a specific type of context and compresses it
|
|
4
4
|
for injection into the prompt. Layers are pluggable and ordered.
|
|
5
5
|
|
|
6
6
|
Layer Architecture:
|
|
7
|
-
L0:
|
|
8
|
-
L1:
|
|
9
|
-
L2:
|
|
10
|
-
L3:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
L0: Constitution — Compressed governance rules (TTL: 300s)
|
|
8
|
+
L1: Department — Detected department from input (no cache)
|
|
9
|
+
L2: Agent — Agent profile + last gotchas (TTL: 30s)
|
|
10
|
+
L3: Project — Active project context (TTL: 30s)
|
|
11
|
+
L3.5: KnowledgeRetrieval — Semantic search from vector DB (TTL: 30s)
|
|
12
|
+
L4: Branch — Current git branch (no cache)
|
|
13
|
+
L5: Command Hints — Matching commands from registry (TTL: 30s)
|
|
14
|
+
L6: Quality Gate — QG status and last verdicts (TTL: 60s)
|
|
15
|
+
L7: Time — Time-of-day signal (no cache)
|
|
15
16
|
"""
|
|
16
17
|
|
|
17
18
|
import re
|
|
@@ -439,3 +440,88 @@ class TimeLayer(Layer):
|
|
|
439
440
|
layer_id=self.id, tag=tag, content=period,
|
|
440
441
|
tokens_est=1, compute_ms=ms, cached=False,
|
|
441
442
|
)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
# --- L3.5: Knowledge Retrieval ---
|
|
446
|
+
|
|
447
|
+
class KnowledgeRetrievalLayer(Layer):
|
|
448
|
+
"""L3.5: Semantic knowledge retrieval from vector DB.
|
|
449
|
+
|
|
450
|
+
Searches the local vector store for chunks relevant to the user's
|
|
451
|
+
input and injects them as context. Gracefully skips if vector store
|
|
452
|
+
is unavailable or empty.
|
|
453
|
+
"""
|
|
454
|
+
|
|
455
|
+
def __init__(self, vector_store: Any = None, max_chunks: int = 3, max_tokens: int = 400) -> None:
|
|
456
|
+
self._store = vector_store
|
|
457
|
+
self._max_chunks = max_chunks
|
|
458
|
+
self._max_tokens = max_tokens
|
|
459
|
+
|
|
460
|
+
@property
|
|
461
|
+
def id(self) -> str:
|
|
462
|
+
return "L3.5"
|
|
463
|
+
|
|
464
|
+
@property
|
|
465
|
+
def name(self) -> str:
|
|
466
|
+
return "KnowledgeRetrieval"
|
|
467
|
+
|
|
468
|
+
@property
|
|
469
|
+
def cache_ttl(self) -> int:
|
|
470
|
+
return 30
|
|
471
|
+
|
|
472
|
+
@property
|
|
473
|
+
def priority(self) -> int:
|
|
474
|
+
return 35
|
|
475
|
+
|
|
476
|
+
def compute(self, ctx: PromptContext) -> LayerResult:
|
|
477
|
+
start = time.time()
|
|
478
|
+
|
|
479
|
+
if not self._store or not ctx.user_input:
|
|
480
|
+
return LayerResult(
|
|
481
|
+
layer_id=self.id, tag="", content="",
|
|
482
|
+
tokens_est=0, compute_ms=0, cached=False,
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
try:
|
|
486
|
+
results = self._store.search(ctx.user_input, top_k=self._max_chunks)
|
|
487
|
+
except Exception:
|
|
488
|
+
return LayerResult(
|
|
489
|
+
layer_id=self.id, tag="", content="",
|
|
490
|
+
tokens_est=0, compute_ms=0, cached=False,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
if not results:
|
|
494
|
+
ms = int((time.time() - start) * 1000)
|
|
495
|
+
return LayerResult(
|
|
496
|
+
layer_id=self.id, tag="", content="",
|
|
497
|
+
tokens_est=0, compute_ms=ms, cached=False,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
# Build compact knowledge context
|
|
501
|
+
snippets = []
|
|
502
|
+
total_tokens = 0
|
|
503
|
+
for r in results:
|
|
504
|
+
text = r["text"][:200].replace("\n", " ").strip()
|
|
505
|
+
tokens = len(text.split())
|
|
506
|
+
if total_tokens + tokens > self._max_tokens:
|
|
507
|
+
break
|
|
508
|
+
source = r.get("source", "").split("/")[-1] if r.get("source") else ""
|
|
509
|
+
snippet = f"{source}: {text}" if source else text
|
|
510
|
+
snippets.append(snippet)
|
|
511
|
+
total_tokens += tokens
|
|
512
|
+
|
|
513
|
+
if not snippets:
|
|
514
|
+
ms = int((time.time() - start) * 1000)
|
|
515
|
+
return LayerResult(
|
|
516
|
+
layer_id=self.id, tag="", content="",
|
|
517
|
+
tokens_est=0, compute_ms=ms, cached=False,
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
content = " | ".join(snippets)
|
|
521
|
+
tag = f"[knowledge:{len(snippets)} chunks]"
|
|
522
|
+
ms = int((time.time() - start) * 1000)
|
|
523
|
+
|
|
524
|
+
return LayerResult(
|
|
525
|
+
layer_id=self.id, tag=tag, content=content,
|
|
526
|
+
tokens_est=total_tokens, compute_ms=ms, cached=False,
|
|
527
|
+
)
|
|
Binary file
|
package/core/tasks/schema.py
CHANGED
|
@@ -29,6 +29,7 @@ class TaskType(str, Enum):
|
|
|
29
29
|
RESEARCH = "research" # Background research
|
|
30
30
|
GENERATION = "generation" # AI content/image generation
|
|
31
31
|
EXPORT = "export" # Export to external system
|
|
32
|
+
KB_INDEX = "kb_index" # Index documents into vector store
|
|
32
33
|
CUSTOM = "custom"
|
|
33
34
|
|
|
34
35
|
|
|
@@ -47,6 +48,12 @@ class Task(BaseModel):
|
|
|
47
48
|
output_data: dict[str, Any] = Field(default_factory=dict)
|
|
48
49
|
output_path: str = "" # File path for output
|
|
49
50
|
|
|
51
|
+
# Budget
|
|
52
|
+
tokens_estimated: int = 0 # Pre-execution estimate
|
|
53
|
+
tokens_actual: int = 0 # Post-execution actual
|
|
54
|
+
budget_approved: bool = True # True if within budget or approved
|
|
55
|
+
approved_by: str = "" # Tier 0 agent who approved overrun
|
|
56
|
+
|
|
50
57
|
# Progress
|
|
51
58
|
progress_percent: int = 0 # 0-100
|
|
52
59
|
progress_message: str = ""
|
|
Binary file
|
|
Binary file
|
package/core/workflow/engine.py
CHANGED
|
@@ -48,6 +48,8 @@ class WorkflowEngine:
|
|
|
48
48
|
on_phase_complete: Optional[Callable[[Phase, PhaseResult], None]] = None,
|
|
49
49
|
on_gate_check: Optional[Callable[[Gate], GateResult]] = None,
|
|
50
50
|
on_visibility: Optional[Callable[[str], None]] = None,
|
|
51
|
+
budget_manager: Any = None,
|
|
52
|
+
obsidian_writer: Any = None,
|
|
51
53
|
):
|
|
52
54
|
"""Initialize the workflow engine.
|
|
53
55
|
|
|
@@ -61,6 +63,8 @@ class WorkflowEngine:
|
|
|
61
63
|
self._on_phase_complete = on_phase_complete
|
|
62
64
|
self._on_gate_check = on_gate_check
|
|
63
65
|
self._on_visibility = on_visibility
|
|
66
|
+
self._budget_manager = budget_manager
|
|
67
|
+
self._obsidian_writer = obsidian_writer
|
|
64
68
|
self._history: list[PhaseResult] = []
|
|
65
69
|
|
|
66
70
|
def announce(self, message: str) -> None:
|
|
@@ -144,6 +148,22 @@ class WorkflowEngine:
|
|
|
144
148
|
if self._on_phase_complete:
|
|
145
149
|
self._on_phase_complete(phase, result)
|
|
146
150
|
|
|
151
|
+
# Save outputs to Obsidian vault (NON-NEGOTIABLE: obsidian-output)
|
|
152
|
+
if self._obsidian_writer and hasattr(phase, "outputs"):
|
|
153
|
+
for output in getattr(phase, "outputs", []):
|
|
154
|
+
obsidian_path = getattr(output, "obsidian_path", "")
|
|
155
|
+
if obsidian_path and result.output:
|
|
156
|
+
try:
|
|
157
|
+
saved = self._obsidian_writer.save(
|
|
158
|
+
obsidian_path=obsidian_path,
|
|
159
|
+
content=result.output,
|
|
160
|
+
department=workflow.department,
|
|
161
|
+
workflow=workflow.id,
|
|
162
|
+
)
|
|
163
|
+
self.announce(f"Saved to vault: {saved.name}")
|
|
164
|
+
except Exception as e:
|
|
165
|
+
self.announce(f"Vault save failed: {e}")
|
|
166
|
+
|
|
147
167
|
self.announce(f"Phase {i}: {phase.name} — COMPLETED")
|
|
148
168
|
|
|
149
169
|
# Evaluate gate
|
|
@@ -189,12 +209,36 @@ class WorkflowEngine:
|
|
|
189
209
|
if gate.type == GateType.AUTO:
|
|
190
210
|
return GateResult(passed=True, gate_type=GateType.AUTO, message="Auto-pass")
|
|
191
211
|
|
|
212
|
+
if gate.type == GateType.BUDGET_CHECK and self._budget_manager:
|
|
213
|
+
return self._evaluate_budget_gate(gate)
|
|
214
|
+
|
|
192
215
|
if self._on_gate_check:
|
|
193
216
|
return self._on_gate_check(gate)
|
|
194
217
|
|
|
195
218
|
# Default: pass
|
|
196
219
|
return GateResult(passed=True, gate_type=gate.type, message="Default pass")
|
|
197
220
|
|
|
221
|
+
def _evaluate_budget_gate(self, gate: Gate) -> GateResult:
|
|
222
|
+
"""Evaluate a budget gate using the budget manager."""
|
|
223
|
+
# Default tier for budget checks (can be overridden via gate metadata)
|
|
224
|
+
tier = 2
|
|
225
|
+
estimated_tokens = 50_000 # Default estimate per phase
|
|
226
|
+
|
|
227
|
+
if self._budget_manager.check_budget(tier, estimated_tokens):
|
|
228
|
+
needs_approval = self._budget_manager.needs_approval(tier)
|
|
229
|
+
msg = "Budget OK"
|
|
230
|
+
if needs_approval:
|
|
231
|
+
msg = "Budget OK (>80% used — Tier 0 notified)"
|
|
232
|
+
self.announce(f"Budget warning: tier {tier} at >80% monthly usage")
|
|
233
|
+
return GateResult(passed=True, gate_type=GateType.BUDGET_CHECK, message=msg)
|
|
234
|
+
|
|
235
|
+
summary = self._budget_manager.get_summary(tier)
|
|
236
|
+
return GateResult(
|
|
237
|
+
passed=False,
|
|
238
|
+
gate_type=GateType.BUDGET_CHECK,
|
|
239
|
+
message=f"Budget exceeded: {summary.used}/{summary.allocated} tokens used ({summary.percent_used}%). Needs Tier 0 approval.",
|
|
240
|
+
)
|
|
241
|
+
|
|
198
242
|
def _evaluate_condition(self, condition: str) -> bool:
|
|
199
243
|
"""Evaluate a skip/branch condition.
|
|
200
244
|
|
package/core/workflow/schema.py
CHANGED
|
@@ -27,6 +27,7 @@ class GateType(str, Enum):
|
|
|
27
27
|
QUALITY_GATE = "quality_gate" # Marta + Eduardo + Francisca
|
|
28
28
|
AUTO = "auto" # Passes automatically if phase succeeds
|
|
29
29
|
CONDITION = "condition" # Passes if condition evaluates true
|
|
30
|
+
BUDGET_CHECK = "budget_check" # Verifies token budget before execution
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
class Gate(BaseModel):
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
id: research-assistant
|
|
2
|
+
name: Maria
|
|
3
|
+
role: Research Assistant
|
|
4
|
+
department: dev
|
|
5
|
+
tier: 3
|
|
6
|
+
|
|
7
|
+
behavioral_dna:
|
|
8
|
+
disc:
|
|
9
|
+
primary: C
|
|
10
|
+
secondary: S
|
|
11
|
+
communication_style: "Thorough, detail-oriented, presents findings systematically"
|
|
12
|
+
under_pressure: "Digs deeper into data before responding"
|
|
13
|
+
motivator: "Understanding the full picture"
|
|
14
|
+
enneagram:
|
|
15
|
+
type: 5
|
|
16
|
+
wing: 6
|
|
17
|
+
core_motivation: "To understand and be competent"
|
|
18
|
+
core_fear: "Being ignorant or uninformed"
|
|
19
|
+
subtype: social
|
|
20
|
+
big_five:
|
|
21
|
+
openness: 90
|
|
22
|
+
conscientiousness: 85
|
|
23
|
+
extraversion: 30
|
|
24
|
+
agreeableness: 70
|
|
25
|
+
neuroticism: 35
|
|
26
|
+
mbti:
|
|
27
|
+
type: INTP
|
|
28
|
+
|
|
29
|
+
authority:
|
|
30
|
+
veto: false
|
|
31
|
+
approve_budget: false
|
|
32
|
+
approve_architecture: false
|
|
33
|
+
approve_quality: false
|
|
34
|
+
block_release: false
|
|
35
|
+
block_delivery: false
|
|
36
|
+
orchestrate: false
|
|
37
|
+
delegates_to: []
|
|
38
|
+
escalates_to: tech-lead-paulo
|
|
39
|
+
|
|
40
|
+
expertise:
|
|
41
|
+
domains: ["research", "documentation", "analysis", "literature-review"]
|
|
42
|
+
frameworks: ["Systematic Review", "PRISMA", "Research Methodology"]
|
|
43
|
+
depth: proficient
|
|
44
|
+
years_equivalent: 5
|
|
45
|
+
|
|
46
|
+
communication:
|
|
47
|
+
language: en
|
|
48
|
+
tone: "Precise and informative"
|
|
49
|
+
vocabulary_level: specialist
|
|
50
|
+
preferred_format: "Structured reports with citations"
|
|
51
|
+
avoid: ["assumptions without evidence", "vague conclusions"]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
id: data-collector
|
|
2
|
+
name: Tomas Jr
|
|
3
|
+
role: Data Collector
|
|
4
|
+
department: kb
|
|
5
|
+
tier: 3
|
|
6
|
+
|
|
7
|
+
behavioral_dna:
|
|
8
|
+
disc:
|
|
9
|
+
primary: C
|
|
10
|
+
secondary: D
|
|
11
|
+
communication_style: "Data-driven, factual, structured"
|
|
12
|
+
under_pressure: "Relies on systematic data collection"
|
|
13
|
+
motivator: "Complete and accurate data"
|
|
14
|
+
enneagram:
|
|
15
|
+
type: 6
|
|
16
|
+
wing: 5
|
|
17
|
+
core_motivation: "To have reliable information"
|
|
18
|
+
core_fear: "Making decisions on incomplete data"
|
|
19
|
+
subtype: self-preservation
|
|
20
|
+
big_five:
|
|
21
|
+
openness: 70
|
|
22
|
+
conscientiousness: 88
|
|
23
|
+
extraversion: 35
|
|
24
|
+
agreeableness: 65
|
|
25
|
+
neuroticism: 40
|
|
26
|
+
mbti:
|
|
27
|
+
type: ISTJ
|
|
28
|
+
|
|
29
|
+
authority:
|
|
30
|
+
veto: false
|
|
31
|
+
approve_budget: false
|
|
32
|
+
approve_architecture: false
|
|
33
|
+
approve_quality: false
|
|
34
|
+
block_release: false
|
|
35
|
+
block_delivery: false
|
|
36
|
+
orchestrate: false
|
|
37
|
+
delegates_to: []
|
|
38
|
+
escalates_to: kb-lead-clara
|
|
39
|
+
|
|
40
|
+
expertise:
|
|
41
|
+
domains: ["data-collection", "web-scraping", "API-integration", "data-validation"]
|
|
42
|
+
frameworks: ["ETL", "Data Quality Framework"]
|
|
43
|
+
depth: proficient
|
|
44
|
+
years_equivalent: 4
|
|
45
|
+
|
|
46
|
+
communication:
|
|
47
|
+
language: en
|
|
48
|
+
tone: "Factual and precise"
|
|
49
|
+
vocabulary_level: specialist
|
|
50
|
+
preferred_format: "Data tables with quality scores"
|
|
51
|
+
avoid: ["subjective interpretations", "unverified claims"]
|