Imported from ahmedmaherpasha1/harness-hack (
AGENTS.md). Install upstream withnpx skills add ahmedmaherpasha1/harness-hack. Copyright stays with the author.
AGENTS.md — AI Agent Platform Guide
For AI agents and developers working on this codebase. This file is prescriptive: it describes what the current architecture requires. Historical bug stories live in
docs/architecture/LEARNINGS.md. Deployment operations live indocs/DEPLOYMENT.md.
1. Non-Negotiable Architecture Rules
-
Keep structure flat and predictable. AI agents navigate by folder name; avoid deep nesting unless there is a clear architectural boundary.
-
One concept = one canonical place. Do not create duplicate routers, duplicate registries, duplicate DB helpers, or parallel state stores.
-
No per-request mutation on singletons. Agents and tools are shared across all requests/workspaces. Per-request data goes through
RunContext. -
Socket.IO is the realtime path. Do not reintroduce raw
/wshandlers. -
chat_messagesis the event log. Do not recreateworkspace_assets,tool_outputs, or new asset-like tables. -
Clerk is the only auth system. No legacy JWT, no Facebook OAuth auth flow.
-
Backend entrypoint is
asgi:application. Socket.IO requires the ASGI wrapper; do not runuvicorn main:app. -
Use migrations. Schema changes go in
backend/migrations/*.sql; do not bury DDL in request handlers. -
Do not store locked system prompts in DB. Only user customization (Layer 4) is editable/stored.
-
Do not create Markdown docs unless asked. This repository already has doc sprawl; update existing docs when useful.
-
runtime/is platform;agents/andtools/are product.backend/runtime/must never import fromagents.*ortools.*. Use__import__()for deferred cross-boundary lookups. Enforced bytests/test_runtime_isolation.py. -
Every tool must declare
idempotent: bool. Default isFalse(safe). Read-only/search tools setTrue. Side-effect tools (facebook_post,image_gen) stayFalse. Resume logic uses this to decide replay vs failure-injection. -
agent_runsis the first-class run entity. A run's lifecycle, status, checkpoints, and cancellation are tracked inagent_runs+agent_checkpoints. Do not invent a parallel tracking mechanism. -
Persist partial state before side effects, not after. The
before_toolcheckpoint is written before calling a tool;after_toolis written after. If the process dies between them, resume uses the tool'sidempotentflag to decide what to do. -
AgentSpecis the canonical descriptor. Add agents by adding anAgentSpec(and aForkSpecif the agent is fork-capable).agents/specs/__init__.pyis the single registry. Do not invent parallel agent definition systems —FORKSis derived from / mirrored byAgentSpec, never the other way around. -
Every fork-capable agent must declare a
ScopePolicy. Enforced bytests/test_agent_spec_invariants.py. Scope is subject-vs-task aware (refuse on TASK, never on SUBJECT). The harness is split intoBASE_SAFETY_RULES(always on),ROUTER_ROLE_BOUNDARY(router only), andFORK_TASK_PREAMBLE(fork only) — picked viabuild_full_prompt(role=...). Forks NEVER see the router's role boundary; that was the source of the 2026-05-02 mid-task false-positive refusal class. -
deliver_sectionis the single phased-delivery primitive. The schema + handler are built byruntime/effects/phased_delivery.py::build_for_frame. Both the cold fork path (runtime/driver.py::_run_child) and the HITL resume path (runtime/resume_paths.py::AwaitingHumanResume) inject it viaFrameBuilder.assemble_full_fork_frame— never inline the injection or mutate the singleton. The socket layer (core/socketio.pyevent_type=='deliver_section') remains the single owner of artifact persistence. -
Fork-mode and DM-mode Layer-4 defaults must be separated for any specialist whose campaign default differs from its standalone behavior. A specialist that the orchestrator forks with a fixed channel mix (e.g. Content Designer = IG + FB by default) MUST NOT carry that fixed mix into its DM-mode prompt — the user driving a DM owns the platform list. Pattern: define both
<NAME>_DEFAULT_USER_CUSTOMIZATION(platform-agnostic, DM mode) and<NAME>_FORK_DEFAULT_USER_CUSTOMIZATION(campaign defaults, AM-fork mode). WireFORK_DEFAULT_USER_CUSTOMIZATIONS["<id>"] = <NAME>_FORK_DEFAULT_USER_CUSTOMIZATIONandFORKS["<id>"].default_user_customization = <NAME>_DEFAULT_USER_CUSTOMIZATION. Layer-2 fork prompts (*_FORK_PROMPT) MUST be platform-flexible — language like "EXACTLY 2 phases" or "Instagram first, then Facebook second" baked into Layer 2 will override the user's intent in DM mode and is forbidden. See §7 "Two defaults: fork-mode vs DM-mode" and the 2026-05-02 Content Designer regression inLEARNINGS.md§E. -
StandaloneShellpassestotal_phases=0(dynamic) tobuild_deliver_section_tool. DM mode cannot pre-compute the section count because the user drives the platform list. Fork mode passestotal_phases=len(fork_phases)because the orchestrator has chosen the channel mix. Hardcodinglen(fork_phases)in the DM path was the proximate cause of the Content Designer producing both IG and FB when the user asked only for IG. -
DM Memory v1 is the single memory model for all agent channels (AM and DM alike).
runtime/dm_memory.pyownsbuild_dm_context(),maybe_compact_memory(), and snapshot read/write. Memory context is injected into the system prompt beforechat()calls — there is no longer a separate MEMORY_RECALL state or chapter lifecycle. Theworkspace_chapterstable andruntime/chapters.pyhave been removed (migration 0022). -
runtime/must never import fromagents.*ortools.*at module level.runtime/dm_memory.pyandruntime/facts.pyare platform modules. Deferred cross-boundary lookups (if ever needed) must use__import__(). Enforced bytests/test_runtime_isolation.py. -
FrameBuilder.assemble_full_fork_frameis the single entry point for constructing a fork frame. Both the cold path (runtime/driver.py::_run_child) and the HITL resume path (runtime/resume_paths.py::AwaitingHumanResume) must call it. Never inline fork frame construction or assemble the 4-layer system prompt outside this method. Parity is enforced bytests/test_frame_builder.py::TestAssembleFullForkFrame. Any future fork ceremony (new per-fork tool injection, prompt layer, etc.) goes intoassemble_full_fork_frame— not into_run_childorAwaitingHumanResumeseparately. -
ResumeDispatcherowns all resume routing.runtime/resume_run()delegates exclusively toruntime/resume_dispatch.py::ResumeDispatcher, which mapscheckpoint.kind→ strategy:AWAITING_HUMAN→AwaitingHumanResume(HITL approve/reject), everything else →PausedTurnResume. Ainject_user_replyparameter routes parent-wake resumes (child completed →_maybe_resume_parent) toPausedTurnResumeinstead ofAwaitingHumanResume. Never callagent.resume()directly fromresume_run()— that was the root of the 2026-05-07 HITL fork double-prompt class. -
runs.transition()is the single trigger for parent wakeup. When a child run transitions toCOMPLETEDorFAILED,runs.transition()schedules_maybe_resume_parentviaasyncio.ensure_future. No other code path should call_maybe_resume_parentproactively (the old unconditional call inresume_runwas Bug B — the AM saying "Hi" after approve).AwaitingHumanResumeandPausedTurnResumeboth transition the run toCOMPLETEDafter the driver loop finishes so this hook fires correctly. -
HITL resume never re-invokes the LLM for the approval itself. The socket layer detects an open interrupt → resolves it → schedules
resume_run(child_run_id).AwaitingHumanResumefeedsHumanResolved(decision)directly into the existingAWAIT_HUMANreducer state — no LLM call, no new user-visible message. Any path that routes an "approve" message through the AM's LLM is a bug. Regression test:tests/e2e/test_hitl_fork_lifecycle.py::test_am_not_awoken_prematurely_after_approve.
2. DM Agent Intelligence (StandaloneShell path)
Full implementation detail lives in
specs/smarter-dm-agent-plan.mdandIMPLEMENTATION_PLAN.md.
-
build_full_prompt(role="dm")is the DM prompt variant. DM agents getBASE_SAFETY_RULES+DM_ROLE_PREAMBLE(~200 tokens) only.ROUTER_ROLE_BOUNDARYis dropped. Never userole="router"for a DM agent — it adds ~1.5k tokens of irrelevant fork-routing rules and causes false refusals. -
DM_REASONING_GUIDANCEis the agent's self-correction contract. It lives inagents/prompts.pyand is injected as part of the DM prompt. It defines: discover-before-act, search-before-id, continuity rule (retry on "try again" — never restart), and cross-agent artifact lookup. Do not duplicate these rules inline in agent-specific prompts. -
The Situation block is prepended to Layer 3 every turn.
runtime/situation.py::build_situation_block()produces an at-a-glance snapshot: connected integrations, last tool error, local time, artifact count. It is assembled from thecomposio_toolslist already in hand (no extra DB call) — connected platforms are derived by splitting tool names on_(e.g.FACEBOOK_POST_TO_PAGE→facebook). Never read a separateworkspace_integrationstable; the tools list is the ground truth. -
Integration snapshot comes from
composio_tools, not the DB.StandaloneShell.chat()receivescomposio_tools(already filtered, status=ACTIVE). The situation block derives connected slugs from this list and shows all social platforms (facebook,instagram,linkedin,twitter,tiktok,snapchat) with ✓/✗ status. This is always in sync with what the agent can actually call. -
friendly_error_message()fires integration-specific recovery hints before generic messages.runtime/error_classifier.py::_recovery_hint()matches known error patterns (no Facebook page, artifact not found, schedule time in past, etc.) and injects an actionable next step (e.g. "callfacebook_list_pagesthen retry withpage_id=<id>"). Add new patterns here — never inline recovery instructions in tool code. -
Skills are lazy-loaded markdown files, not hardcoded prompt blocks.
backend/agents/skills/holds Hermes-style.mdfiles with YAML frontmatter.agents/skill_loader.py::load_skill()reads them on demand. Theload_skilltool lets the agent pull a skill mid-conversation. Do not add new static prompt blocks for domain knowledge — write a skill file instead. -
search_artifacts(scope="all")is the cross-agent artifact lookup. When an agent can't find an artifact by ID, it must callsearch_artifactsbefore giving up or creating a new one.scope="all"returns artifacts from all agents in the workspace. Thecreated_by_agentfield in results tells the agent which specialist produced it. This is the fix for agents creating duplicate artifacts instead of finding existing ones.
Stripe locally
brew install stripe/stripe-cli/stripe
2. Log in once
stripe login
-- connect to sandbox
3. Forward to your local backend (leave running in a separate terminal)
stripe listen --forward-to localhost:8000/api/billing/webhook When you run step 3, Stripe CLI prints a line like:
Ready! Your webhook signing secret is whsec_xxxxxxxxxxxxxxxx