Skip to content

Skill System

The skill system provides formal abstractions for Android automation. It is defined in marketing_system/skills/base.py (262 lines) and includes five core classes.

Return value from action execution:

@dataclass
class ActionResult:
success: bool
data: dict = field(default_factory=dict)
error: str = ""
duration_ms: float = 0.0

ActionResult is truthy when success=True, so you can use it in conditionals: if result: ...

UI locator with fallback chain:

class Element:
# Locator fields (tried in this order):
content_desc: str
text: str
resource_id: str
class_name: str
x: int # absolute fallback
y: int
def find(self, device, xml) -> tuple[int, int] | None:
"""Try each locator in priority order, return center coords."""

Atomic operation with pre/post validation:

class Action:
name: str
description: str
max_retries: int = 2
retry_delay: float = 1.0
def precondition(self, dev, xml) -> bool: ... # Override to validate state
def execute(self, dev, **kwargs) -> ActionResult: ... # Must implement
def postcondition(self, dev, xml) -> bool: ... # Override to verify success
def rollback(self, dev) -> None: ... # Override to undo on failure
def run(self, dev, **kwargs) -> ActionResult: ... # Orchestrator

Composed action sequence:

class Workflow:
name: str
description: str
params: dict
def steps(self) -> list: ... # Override: return [(action_name, params), ...]
def run(self, dev) -> ActionResult: ... # Execute all steps in order

Top-level container that loads from YAML:

class Skill:
name: str
app_package: str
version: str
elements: dict[str, Element]
def register_action(self, action_cls): ...
def register_workflow(self, workflow_cls): ...
def get_action(self, name, device) -> Action: ...
def get_workflow(self, name, device, **params) -> Workflow: ...
def list_actions(self) -> list[str]: ...
def list_workflows(self) -> list[str]: ...
@classmethod
def from_yaml(cls, path) -> Skill: ... # Load skill.yaml + elements.yaml
precondition() -> False? -> return failure immediately
| True
v
execute() -> loop up to max_retries (default 2):
| success + postcondition() True? -> return success
| failure? -> rollback(), retry after retry_delay (default 1.0s)
v
All retries exhausted -> return failure with last error
for action in steps():
result = action.run()
if not result:
return failure (completed_steps count + failed_step name)
return success (total completed_steps)
skills/tiktok/
skill.yaml # metadata: name, version, app_package, description
elements.yaml # 41 UI elements with fallback locator chains
__init__.py # load() function registers actions + workflows
actions/
__init__.py
core.py # OpenApp, NavigateToProfile, TapSearch, TypeAndSearch, DismissPopup
engagement.py # LikePost, CommentOnPost, FollowUser, ScrollFeed, TapUser, etc.
workflows/
__init__.py
upload_video.py
crawl_hashtag.py
send_dm.py
engage_fyp.py
... (9 total)
ActionFileHas Postcondition
open_appcore.pyYes (checks TIKTOK_PKG in dumpsys)
navigate_to_profilecore.pyYes (checks followers/Following)
tap_searchcore.pyYes (checks search box RID)
type_and_searchcore.pyNo
dismiss_popupcore.pyNo
like_postengagement.pyNo
comment_on_postengagement.pyNo
follow_userengagement.pyNo
scroll_feedengagement.pyNo
tap_userengagement.pyNo
tap_message_buttonengagement.pyNo
type_messageengagement.pyNo
tap_sendengagement.pyNo
WorkflowWraps
upload_videobots/tiktok/upload.py (43-step flow)
crawl_hashtagbots/tiktok/scraper.py --tab top
crawl_usersbots/tiktok/scraper.py --tab users
send_dmbots/tiktok/outreach.py
engage_fypbots/tiktok/engage.py
scan_inboxbots/tiktok/inbox_scanner.py
scrape_analyticsbots/tiktok/perf_scanner.py
continuous_scrapebots/tiktok/continuous_scrape.py
publish_draftbots/tiktok/upload.py --publish-draft

The _base skill provides generic actions usable by any app: tap_element, swipe_direction, type_text, wait, back, home, launch_app, take_screenshot, and press_enter.

When skills are executed via the scheduler or API, they run as subprocesses:

Terminal window
python3 skills/_run_skill.py \
--skill tiktok \
--workflow engage_fyp \
--device L9AIB7603188953 \
--params '{"duration": 60}'

This keeps the server responsive and provides process isolation.

FileLinesPurpose
skills/base.py262ActionResult, Element, Action, Workflow, Skill
skills/tiktok/13 actions + 9 workflows + 41 elements
skills/_base/9 shared actions
skills/instagram/Skeleton (skill.yaml only)
skills/_run_skill.pyCLI runner for job queue