use crate::core::container_runtime;
use crate::core::docs::{DocFragment, Mandate};
use serde::{Deserialize, Serialize};
use sha2::Digest;
use std::collections::HashMap;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct OrientationPacket {
pub user_goal: String,
pub task_id: Option<String>,
pub constraints: Vec<String>,
pub allowed_scope: Vec<String>,
pub forbidden_scope: Vec<String>,
pub relevant_areas: Vec<String>,
pub proof_required: Vec<String>,
pub known_unknowns: Vec<String>,
pub decision_gates: Vec<DecisionGate>,
pub next_action: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DecisionGate {
pub decision: String,
pub rationale: String,
pub options: Vec<DecisionOption>,
pub recommendation: String,
pub validation_proof: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DecisionOption {
pub label: String,
pub impact: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RpcRequest {
pub op: String,
#[serde(default)]
pub params: serde_json::Value,
#[serde(default = "default_request_id")]
pub id: String,
#[serde(default)]
pub session: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentInitParams {}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentInitResult {
pub environment_context: EnvironmentContext,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct EnvironmentContext {
pub repo_root: String,
pub workspace_path: String,
pub tool_summary: ToolSummary,
pub done_means: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ToolSummary {
pub docker_available: bool,
pub in_container: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct WorkspaceStatusParams {}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct WorkspaceStatusResult {
pub git_branch: String,
pub git_is_protected: bool,
pub in_container: bool,
pub can_work: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct WorkspaceEnsureParams {
pub branch: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct WorkspaceEnsureResult {
pub branch: String,
pub worktree_path: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct WorkspacePublishParams {
pub title: Option<String>,
pub description: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct WorkspacePublishResult {
pub branch: String,
pub commit_hash: String,
pub remote_url: String,
pub pr_url: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ContextResolveParams {
pub op: Option<String>,
pub touched_paths: Option<Vec<String>>,
pub intent_tags: Option<Vec<String>>,
pub query: Option<String>,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ContextResolveResult {
pub fragments: Vec<DocFragment>,
pub scoped_fragments: Vec<DocFragment>,
pub local_project_specs: LocalProjectSpecs,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LocalProjectSpecs {
pub canonical_paths: Vec<String>,
pub constitution_refs: Vec<String>,
pub intent: Option<String>,
pub architecture: Option<String>,
pub interfaces: Option<String>,
pub validation: Option<String>,
pub update_guidance: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ContextCapsuleQueryParams {
pub topic: String,
pub scope: String,
pub task_id: Option<String>,
pub workunit_id: Option<String>,
pub limit: Option<usize>,
pub risk_tier: Option<String>,
pub write: Option<bool>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ContextBindingsParams {}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ConstitutionGetParams {
pub section: String,
pub subsection: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ConstitutionGetResult {
pub section: String,
pub title: String,
pub category: String,
pub dependencies: Vec<String>,
pub content: serde_json::Value,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SchemaGetParams {
pub entity: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SchemaGetResult {
pub schema_version: String,
pub json_schema: serde_json::Value,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct StoreUpsertParams {
pub entity: Option<String>,
pub payload: Option<serde_json::Value>,
pub provenance: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct StoreUpsertResult {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stored: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub action: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct StoreQueryParams {
pub entity: Option<String>,
pub query: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct StoreQueryResult {
pub items: Vec<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_page: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ValidateRunParams {
pub gate: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ValidateRunResult {
pub success: bool,
pub report: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ScaffoldNextQuestionParams {
pub project_name: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ScaffoldNextQuestionResult {
pub interview_id: String,
pub question: Option<crate::core::interview::Question>,
pub complete: Option<bool>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ScaffoldApplyAnswerParams {
pub question_id: String,
pub value: serde_json::Value,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ScaffoldApplyAnswerResult {
pub answers_count: usize,
pub is_complete: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ScaffoldGenerateArtifactsParams {}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct StandardsResolveParams {}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MentorObligationsParams {
pub op: Option<String>,
pub params: Option<serde_json::Value>,
pub touched_paths: Option<Vec<String>>,
pub diff_summary: Option<String>,
pub project_profile_id: Option<String>,
pub session_id: Option<String>,
pub high_risk: Option<bool>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MentorObligationsResult {
pub obligations: crate::core::mentor::Obligations,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AssuranceEvaluateParams {
pub op: Option<String>,
pub params: Option<serde_json::Value>,
pub touched_paths: Option<Vec<String>>,
pub diff_summary: Option<String>,
pub session_id: Option<String>,
pub phase: Option<crate::core::assurance::AssurancePhase>,
pub time_budget_s: Option<u64>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AssuranceEvaluateResult {
pub assurance_evaluated: bool,
pub interlock_code: Option<String>,
}
pub fn default_request_id() -> String {
crate::core::ulid::new_ulid()
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RpcResponse {
pub id: String,
pub success: bool,
pub receipt: Receipt,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub mandates: Vec<Mandate>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_capsule: Option<ContextCapsule>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<serde_json::Value>,
pub allowed_next_ops: Vec<AllowedOp>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub blocked_by: Vec<Blocker>,
#[serde(skip_serializing_if = "Option::is_none")]
pub interlock: Option<Interlock>,
#[serde(skip_serializing_if = "Option::is_none")]
pub advisory: Option<Advisory>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attestation: Option<Attestation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<RpcError>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Receipt {
pub op: String,
pub timestamp: String,
pub inputs_hash: String,
pub outputs_hash: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub touched_paths: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub governing_anchors: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ContextCapsule {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub fragments: Vec<DocFragment>,
#[serde(skip_serializing_if = "Option::is_none")]
pub spec: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub architecture: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub security: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub standards: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AllowedOp {
pub op: String,
pub reason: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub required_params: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Blocker {
pub kind: BlockerKind,
pub message: String,
pub resolve_hint: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BlockerKind {
MissingAnswer,
MissingProof,
Unauthorized,
Conflict,
ValidationFailed,
WorkspaceRequired,
ProtectedBranch,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RpcError {
pub code: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct CapabilitiesReport {
pub version: String,
pub capabilities: Vec<Capability>,
pub subsystems: Vec<SubsystemInfo>,
pub workspace: WorkspaceCapabilities,
pub interview: InterviewCapabilities,
pub interlock_codes: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Capability {
pub name: String,
pub description: String,
pub stability: String,
pub cost: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SubsystemInfo {
pub name: String,
pub status: String,
pub ops: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct WorkspaceCapabilities {
pub enforcement_available: bool,
pub docker_available: bool,
pub protected_patterns: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct InterviewCapabilities {
pub available: bool,
pub artifact_types: Vec<String>,
pub standards_resolution: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Interlock {
pub code: String,
pub message: String,
pub unblock_ops: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub evidence: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct EvidenceRef {
pub source: String,
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub hash: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ReconciliationPointer {
pub kind: String,
pub r#ref: String,
pub title: String,
pub why_short: String,
pub evidence: EvidenceRef,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ReconciliationSets {
pub must: Vec<ReconciliationPointer>,
pub recommended: Vec<ReconciliationPointer>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct VerificationPlan {
pub required: Vec<String>,
pub checklist: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LoopSignal {
pub code: String,
pub message: String,
pub suggested_redirect_ops: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Advisory {
pub reconciliations: ReconciliationSets,
pub verification_plan: VerificationPlan,
#[serde(skip_serializing_if = "Option::is_none")]
pub loop_signal: Option<LoopSignal>,
#[serde(skip_serializing_if = "Option::is_none")]
pub notes: Option<Vec<String>>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Attestation {
pub id: String,
pub op: String,
pub timestamp: String,
pub input_hash: String,
pub touched_paths: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub interlock_code: Option<String>,
pub outcome: String,
pub trace_path: String,
}
pub fn generate_capabilities() -> CapabilitiesReport {
let docker_available = container_runtime::container_runtime_available();
CapabilitiesReport {
version: env!("CARGO_PKG_VERSION").to_string(),
capabilities: vec![
Capability {
name: "daemonless".to_string(),
description: "Decapod never runs in the background; it is invoked by agents"
.to_string(),
stability: "stable".to_string(),
cost: "none".to_string(),
},
Capability {
name: "deterministic".to_string(),
description: "Same inputs produce identical outputs given fixed repo state"
.to_string(),
stability: "stable".to_string(),
cost: "none".to_string(),
},
Capability {
name: "context.resolve".to_string(),
description: "Resolve relevant constitution/authority fragments for an operation"
.to_string(),
stability: "stable".to_string(),
cost: "low".to_string(),
},
Capability {
name: "context.scope".to_string(),
description:
"Return scoped, query-matched constitution fragments for just-in-time context"
.to_string(),
stability: "stable".to_string(),
cost: "low".to_string(),
},
Capability {
name: "context.capsule.query".to_string(),
description:
"Return deterministic context capsules scoped to core/interfaces/plugins docs"
.to_string(),
stability: "stable".to_string(),
cost: "low".to_string(),
},
Capability {
name: "schema.get".to_string(),
description: "Get authoritative JSON schemas for entities".to_string(),
stability: "stable".to_string(),
cost: "low".to_string(),
},
Capability {
name: "store.upsert".to_string(),
description: "Deterministic storage for decisions/knowledge/todos".to_string(),
stability: "stable".to_string(),
cost: "medium".to_string(),
},
Capability {
name: "store.query".to_string(),
description: "Retrieve canonical entities deterministically".to_string(),
stability: "stable".to_string(),
cost: "medium".to_string(),
},
Capability {
name: "validate.run".to_string(),
description: "Run deterministic validation gates".to_string(),
stability: "stable".to_string(),
cost: "medium".to_string(),
},
Capability {
name: "workspace.ensure".to_string(),
description: "Create or enter an isolated agent workspace".to_string(),
stability: "stable".to_string(),
cost: "low".to_string(),
},
Capability {
name: "workspace.status".to_string(),
description: "Check current workspace and branch status".to_string(),
stability: "stable".to_string(),
cost: "low".to_string(),
},
Capability {
name: "eval.gate".to_string(),
description: "Run variance-aware statistical promotion gate over eval aggregates"
.to_string(),
stability: "beta".to_string(),
cost: "medium".to_string(),
},
Capability {
name: "preflight.check".to_string(),
description:
"Before any operation, predict what will fail and what context is needed"
.to_string(),
stability: "stable".to_string(),
cost: "low".to_string(),
},
Capability {
name: "impact.predict".to_string(),
description:
"Predict validation outcomes for changed files before running validate"
.to_string(),
stability: "stable".to_string(),
cost: "low".to_string(),
},
Capability {
name: "todo.manage".to_string(),
description: "Add, claim, list, and complete todo tasks".to_string(),
stability: "stable".to_string(),
cost: "low".to_string(),
},
Capability {
name: "session.acquire".to_string(),
description: "Acquire or renew an agent session token".to_string(),
stability: "stable".to_string(),
cost: "low".to_string(),
},
Capability {
name: "constitution.get".to_string(),
description: "Return structured sections from the embedded constitution asset"
.to_string(),
stability: "stable".to_string(),
cost: "low".to_string(),
},
],
subsystems: vec![
SubsystemInfo {
name: "todo".to_string(),
status: "active".to_string(),
ops: vec![
"add".to_string(),
"claim".to_string(),
"done".to_string(),
"list".to_string(),
],
},
SubsystemInfo {
name: "knowledge".to_string(),
status: "active".to_string(),
ops: vec!["add".to_string(), "search".to_string()],
},
SubsystemInfo {
name: "federation".to_string(),
status: "active".to_string(),
ops: vec!["add".to_string(), "get".to_string(), "graph".to_string()],
},
SubsystemInfo {
name: "lcm".to_string(),
status: "active".to_string(),
ops: vec![
"ingest".to_string(),
"list".to_string(),
"show".to_string(),
"summarize".to_string(),
"summary".to_string(),
"schema".to_string(),
"rebuild".to_string(),
],
},
SubsystemInfo {
name: "map".to_string(),
status: "active".to_string(),
ops: vec![
"llm".to_string(),
"agentic".to_string(),
"schema".to_string(),
],
},
SubsystemInfo {
name: "eval".to_string(),
status: "active".to_string(),
ops: vec![
"plan".to_string(),
"ingest-run".to_string(),
"judge".to_string(),
"aggregate".to_string(),
"gate".to_string(),
"bucket-failures".to_string(),
],
},
SubsystemInfo {
name: "infer".to_string(),
status: "active".to_string(),
ops: vec![
"init".to_string(),
"orientation".to_string(),
"validate".to_string(),
"budget".to_string(),
],
},
],
workspace: WorkspaceCapabilities {
enforcement_available: true,
docker_available,
protected_patterns: vec![
"main".to_string(),
"master".to_string(),
"production".to_string(),
"release/*".to_string(),
],
},
interview: InterviewCapabilities {
available: true,
artifact_types: vec![
"spec".to_string(),
"architecture".to_string(),
"security".to_string(),
"ops".to_string(),
"adr".to_string(),
],
standards_resolution: true,
},
interlock_codes: vec![
"workspace_required".to_string(),
"verification_required".to_string(),
"store_boundary_violation".to_string(),
"decision_required".to_string(),
],
}
}
#[allow(clippy::too_many_arguments)]
pub fn success_response(
request_id: String,
op: String,
params: serde_json::Value,
result: Option<serde_json::Value>,
touched_paths: Vec<String>,
context_capsule: Option<ContextCapsule>,
allowed_next_ops: Vec<AllowedOp>,
mandates: Vec<Mandate>,
) -> RpcResponse {
let timestamp = crate::core::time::now_epoch_z();
let inputs_hash = format!(
"{:x}",
sha2::Sha256::digest(serde_json::to_string(¶ms).unwrap_or_default())
);
let outputs_hash = format!(
"{:x}",
sha2::Sha256::digest(serde_json::to_string(&result).unwrap_or_default())
);
RpcResponse {
id: request_id,
success: true,
receipt: Receipt {
op,
timestamp,
inputs_hash,
outputs_hash,
touched_paths,
governing_anchors: mandates.iter().map(|m| m.fragment.r#ref.clone()).collect(),
},
mandates,
context_capsule,
result,
allowed_next_ops,
blocked_by: vec![],
interlock: None,
advisory: None,
attestation: None,
error: None,
}
}
pub fn error_response(
request_id: String,
op: String,
params: serde_json::Value,
code: String,
message: String,
blocker: Option<Blocker>,
mandates: Vec<Mandate>,
) -> RpcResponse {
let timestamp = crate::core::time::now_epoch_z();
let inputs_hash = format!(
"{:x}",
sha2::Sha256::digest(serde_json::to_string(¶ms).unwrap_or_default())
);
let outputs_hash = format!("{:x}", sha2::Sha256::digest("error"));
let blocked_by = if let Some(b) = blocker {
vec![b]
} else {
vec![]
};
RpcResponse {
id: request_id,
success: false,
receipt: Receipt {
op,
timestamp,
inputs_hash,
outputs_hash,
touched_paths: vec![],
governing_anchors: mandates.iter().map(|m| m.fragment.r#ref.clone()).collect(),
},
mandates,
context_capsule: None,
result: None,
allowed_next_ops: vec![AllowedOp {
op: "agent.init".to_string(),
reason: "Session may be invalid or expired".to_string(),
required_params: vec![],
}],
blocked_by,
interlock: None,
advisory: None,
attestation: None,
error: Some(RpcError {
code,
message,
details: None,
}),
}
}